Compare commits

...

57 Commits

Author SHA1 Message Date
JT
9ef65dcd69 Bump to 0.70 (#6800) 2022-10-19 07:13:36 +13:00
JT
f99c002426 Fix let-env in banner (#6795) 2022-10-18 22:42:00 +13:00
f0420c5a6c Pin reedline to the 0.13.0 release (#6789)
See the release notes:

https://github.com/nushell/reedline/releases/tag/v0.13.0
2022-10-17 23:45:28 +02:00
46eec5e3a2 Tolerate more tty acquisition failures in non-interactive mode, fixes #6719 (#6779) 2022-10-17 21:08:25 +02:00
378248341e Update README.md (#6782)
Fixed a very small inconsistency.
2022-10-17 06:23:11 -05:00
803f9d4daf Upgrade reedline to latest dev version (#6778)
* Reorder conditional deps for readability

* Pull reedline from the most recent main branch

* Map 'Submit' and 'SubmitOrNewline' events
  Introduced by nushell/reedline#490
2022-10-16 23:51:15 +02:00
ce809881eb Rename query dfr -> query df (#6777) 2022-10-16 21:04:48 +02:00
1a99893e2d Add documentation requirement to PR template (#6749) 2022-10-16 18:19:54 +03:00
ec8e57cde9 Add search terms to roll commands (#6761) 2022-10-16 13:04:22 +02:00
a498234f1d fix stdout hangged on (#6715) 2022-10-15 14:29:29 -05:00
de77cb0cc4 add filesize_metric comment (#6760) 2022-10-15 14:26:18 -05:00
7d5d53cf85 Add search terms to arg dataframe commands (#6724)
* added search terms for arg prefixed dataframe commands

* remove search terms that already produce results
2022-10-15 12:49:09 -05:00
1572808adb Filter out empty glob patterns to "glob" command (#6707)
* Filter out empty glob patterns

An empty argument to the "glob" command will now produce an empty result.
Working towards nushell/nushell#6653.

* Run `cargo fmt --all`

Just autoformatted the repo so that CI passes and we have a consistent code
format across modules.

* Treat empty glob argument as error

The glob command will now report an empty string argument as an error instead
of silently ignoring it.

See https://github.com/nushell/nushell/pull/6707#discussion_r993345013.

* Add tests for glob command

Two small tests for the glob command, one to check that the empty string errors
it, and another to sanity check the '*' glob, have been added.

* Rename glob sanity check star test

Co-authored-by: Kyle Anderson <kyle.anderson@uwaterloo.ca>
2022-10-15 18:00:38 +02:00
9d77e3fc7c Improve erroring of config nu and config env (#6730)
* improve errors for `config nu` and `config env`

* fix tests
2022-10-15 08:28:54 -05:00
e22f2e9f13 window --remainder (#6738)
* Implement window remainder, and save allocation

* Fallible memory reservation
2022-10-15 08:06:54 -05:00
4ffa4ac42a Delete out.log (#6731) 2022-10-15 07:37:57 -05:00
da6f548dfd Remove unnecessary clone (#6729) 2022-10-14 17:13:24 -05:00
JT
7532991544 Allow auto-cd to work with backticks (#6728) 2022-10-15 10:37:31 +13:00
b7f47317c2 Fix quadratic time complexity with large strides (#6727) 2022-10-14 15:17:47 -05:00
5849e4f6e3 let alias list aliases (#6717)
* let `alias` list aliases

* fix highlighting

* Update parse_keywords.rs
2022-10-14 21:51:44 +02:00
868d94f573 Add search terms for export commands (#6722)
Contributes to https://github.com/nushell/nushell/issues/5093
2022-10-14 12:02:22 -05:00
1344ae3a65 add the ability to convert durations (#6723)
* add the ability to convert durations

* add conversion to floats if necessary
2022-10-14 11:46:48 -05:00
804b155035 Add search terms for uppercase (#6720)
* Add search terms for uppercase

* Add search terms

* Add search terms

* Change to parse

* Add search terms for from

* Add search terms for to

* Remove duplicate function

* Remove duplication of search terms

* Remove search term
2022-10-14 05:36:31 -05:00
9446e3960b Fix invalid variable name in input command docs (#6716) 2022-10-13 12:42:24 -05:00
90ba39184a allow for $in to affect environment (#6649)
* allow for `$in` to affect environment

* fix for vars, overlays, env_hidden

* fmt

* carry over variables properly

* add test

* modify name, remove redundant

* fmt
2022-10-13 12:04:34 +03:00
d40a73aafe Backport fixes from nushell/nushell.github.io#633 (#6712)
Co-authored-by: Eric Hodel <drbrain@segment7.net>

Co-authored-by: Eric Hodel <drbrain@segment7.net>
2022-10-12 19:14:16 +02:00
5815f122ed avoid freeze when capturing external stderr (#6700)
* avoid freeze when capturing external stderr

* try replace from sh to bash

* change description

* fmt code
2022-10-12 08:41:20 -05:00
0bbb3a20df Fix ex. completion git push --force-with-lease (#6702)
The `--force-with-lease` flag was given as requiring an additional string which is not true.

Fixes #6644 

for this to take effect you need to update your `config.nu`
2022-10-11 12:41:50 +02:00
1998bce19f avoid freeze for table print (#6688)
* avoid freeze for table print

* make failed_with_proper_exit_code work again

* add test case for table

* fix un-used import on windows
2022-10-10 07:32:55 -05:00
2f1711f783 return gid and uid in numbers if name not found (#6684)
* return git and uid in numbers if name not found

* fmt
2022-10-10 14:29:16 +02:00
34c8b276ab Return Error on str replace RegEx parse fail (#6695) 2022-10-10 07:27:01 -05:00
fde56cfe99 upgrade num-format (#6694) 2022-10-10 06:25:57 -05:00
118033e4a5 don't attempt to eval and record down if the repl line is empty (#6674) 2022-10-08 16:38:35 -05:00
7910d20e50 add a new command to query the registry on windows (#6670)
* add a new command to query the registry on windows

* cross platform tweaks

* return nushell datatype

* change visibility of exec and registry commands
2022-10-07 13:54:36 -05:00
e1d5180e6d Update nushell version for release workflow (#6666) 2022-10-05 22:10:25 +08:00
79ce13abef To nuon escapes (#6660)
* Add tests for "to nuon" escaping handling

* Fix "to nuon" not escaping double quotations

* Fix "to nuon" double backslash

Fix value_to_string_without_quotes leaving escaped backslash in
non-quoted strings
2022-10-04 06:25:21 -05:00
5921c19bc0 WIP/ Checkout to new tabled (#6286)
* nu-table/ Use latest tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table/ Fix first column alignment

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Fix cargo clippy

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Fix color issue

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Fix footer row

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Update

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table/ Update

* Use latest tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add optional -e, -c argument to `table` command for different view

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix clippy

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix clippy

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Update

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix cargo clippy

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix tests

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Add footer into -e/c mode

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Publish new expand mode

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add width ctrl for Expand mode

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Refactorings

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Refactorings

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add tests

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add tests

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Merge with main

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix clippy

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix tests

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix tests

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add record expand and fix empty list issue

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* refactoring

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-10-03 11:40:16 -05:00
e629ef203a nu-cli: external completer precedence before file (#6652) 2022-10-01 07:24:22 -05:00
5959d1366a Remove unnecessary flags from term size (#6651)
The columns and rows can be obtained individually using

(term size).columns
(term size).rows
2022-10-01 07:00:54 -05:00
530ff3893e Eval external command result immediately when using do command with -c (#6645)
* make capture error works better in do command

* remove into string test because we have no way to generate Value::Error for now
2022-09-30 07:14:02 -05:00
6f59167960 Make semicolon works better for internal commands (#6643)
* make semicolon works with some internal command like do

* refactor, make consume external result logic out of eval_external

* update comment
2022-09-30 07:13:46 -05:00
ca715bb929 tweak the banner message and make the time more accurate (#6641) 2022-09-29 14:07:32 -05:00
4af0a6a3fa Foreground process group management, again (#6584)
* Revert "Revert "Try again: in unix like system, set foreground process while running external command (#6273)" (#6542)"

This reverts commit 2bb367f570.

* Make foreground job control hopefully work correctly

These changes are mostly inspired by the glibc manual.

* Fix typo in external command description

* Only restore tty control to shell when no fg procs are left; reuse pgrp

* Rework terminal acquirement code to be like fish

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-09-29 13:37:48 -05:00
6486364610 changed the way durations and filesizes are parsed (#6640) 2022-09-29 13:24:17 -05:00
6aa8a0073b add better description to table_index_mode (#6637) 2022-09-29 06:26:01 -05:00
f5e1b08e6a ensure Operator::And errors out with incompatible types (#6638) 2022-09-29 06:17:21 -05:00
7b9ad9d2e5 Fix issue 6596 (#6603)
* Fix issue 6596

* add two unit tests

* fix pipe

* add cp test

* fix test on windows
2022-09-29 10:43:58 +02:00
1a3762b905 prevent alias name from being filesize or number (#6595)
* prevent alias name from being filesize or number

* add test

* fmt
2022-09-28 17:08:38 -05:00
32fbcf39cc make first behave same way as last: always return list when with number argument (#6616)
* make `first` behave same way as `last`

* better behaviour

* fix tests

* add tests
2022-09-28 17:08:17 -05:00
dd578926c3 add some float operations with filesize (#6618)
* add some float operations with filesize

* more changes

* update return values on filesize-filesize, duration-duration

* missed filesize in floordiv

* missed float * duration
2022-09-28 17:07:50 -05:00
5c99921e15 Table indexes (#6620)
* Table indexes

* Renamed to `show_table_indexes`

* Renamed to `table_index_mode`
2022-09-28 17:07:33 -05:00
d2e4f03d19 update and fix python plugin example (#6633)
* update and fix python plugin example

* update comment
2022-09-28 17:06:43 -05:00
23bba9935f bump to dev version 0.69.2 (#6635) 2022-09-28 17:06:21 -05:00
JT
8a5abc7afc bump to 0.69.1 (#6631) 2022-09-28 15:48:01 +13:00
JT
ec711cb79d remove -d and -t from touch (#6629)
* remove -d and -t from touch

* remove unused test import
2022-09-28 13:48:34 +13:00
JT
f2ad7fae1f bump to updated reedline (#6626) 2022-09-28 12:08:42 +13:00
JT
13a4474512 Update Cargo.toml 2022-09-28 12:02:39 +13:00
130 changed files with 4585 additions and 2805 deletions

View File

@ -15,3 +15,7 @@ Make sure you've run and fixed any issues with these commands:
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
# Documentation
- [ ] If your PR touches a user-facing nushell feature then make sure that there is an entry in the documentation (https://github.com/nushell/nushell.github.io) for the feature, and update it if necessary.

View File

@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
$'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' {
(do -i { ./output/nu.exe -c 'version' }) | str collect
(do -i { ./output/nu.exe -c 'version' }) | str join
} else {
(do -i { ./output/nu -c 'version' }) | str collect
(do -i { ./output/nu -c 'version' }) | str join
}
if ($ver | str trim | is-empty) {
$'(ansi r)Incompatible nu binary...(ansi reset)'

View File

@ -72,7 +72,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v2.1
with:
version: 0.68.0
version: 0.69.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

796
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,9 @@ exclude = ["images"]
homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.69.0"
version = "0.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -39,21 +38,21 @@ ctrlc = "3.2.1"
log = "0.4"
miette = "5.1.0"
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.69.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.0" }
nu-command = { path="./crates/nu-command", version = "0.69.0" }
nu-engine = { path="./crates/nu-engine", version = "0.69.0" }
nu-json = { path="./crates/nu-json", version = "0.69.0" }
nu-parser = { path="./crates/nu-parser", version = "0.69.0" }
nu-path = { path="./crates/nu-path", version = "0.69.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.0" }
nu-system = { path = "./crates/nu-system", version = "0.69.0" }
nu-table = { path = "./crates/nu-table", version = "0.69.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.69.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
nu-cli = { path="./crates/nu-cli", version = "0.70.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.70.0" }
nu-command = { path="./crates/nu-command", version = "0.70.0" }
nu-engine = { path="./crates/nu-engine", version = "0.70.0" }
nu-json = { path="./crates/nu-json", version = "0.70.0" }
nu-parser = { path="./crates/nu-parser", version = "0.70.0" }
nu-path = { path="./crates/nu-path", version = "0.70.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.70.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.70.0" }
nu-system = { path = "./crates/nu-system", version = "0.70.0" }
nu-table = { path = "./crates/nu-table", version = "0.70.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.70.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
rayon = "1.5.1"
is_executable = "1.0.1"
@ -65,8 +64,16 @@ time = "0.3.12"
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[target.'cfg(target_family = "unix")'.dependencies]
nix = "0.24"
atty = "0.2"
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.69.0" }
nu-test-support = { path="./crates/nu-test-support", version = "0.70.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
@ -75,9 +82,6 @@ hamcrest2 = "0.3.0"
rstest = {version = "0.15.0", default-features = false}
itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[features]
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
extra = ["default", "dataframe", "database"]
@ -123,5 +127,7 @@ debug = false
name = "nu"
path = "src/main.rs"
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }

View File

@ -71,7 +71,7 @@ Additionally, commands can output structured data (you can think of this as a th
Commands that work in the pipeline fit into one of three categories:
- Commands that produce a stream (e.g., `ls`)
- Commands that filter a stream (eg, `where type == "dir"`)
- Commands that filter a stream (e.g., `where type == "dir"`)
- Commands that consume the output of the pipeline (e.g., `table`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.

View File

@ -5,22 +5,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.69.0"
version = "0.70.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.69.0" }
nu-command = { path = "../nu-command", version = "0.69.0" }
nu-test-support = { path="../nu-test-support", version = "0.70.0" }
nu-command = { path = "../nu-command", version = "0.70.0" }
rstest = {version = "0.15.0", default-features = false}
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-parser = { path = "../nu-parser", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
atty = "0.2.14"
chrono = "0.4.21"

View File

@ -60,10 +60,10 @@ impl NuCompleter {
fn external_completion(
&self,
block_id: BlockId,
spans: Vec<String>,
spans: &[String],
offset: usize,
span: Span,
) -> Vec<Suggestion> {
) -> Option<Vec<Suggestion>> {
let stack = self.stack.clone();
let block = self.engine_state.get_block(block_id);
let mut callee_stack = stack.gather_captures(&block.captures);
@ -75,9 +75,9 @@ impl NuCompleter {
var_id,
Value::List {
vals: spans
.into_iter()
.iter()
.map(|it| Value::String {
val: it,
val: it.to_string(),
span: Span::unknown(),
})
.collect(),
@ -109,13 +109,13 @@ impl NuCompleter {
offset,
);
return result;
return Some(result);
}
}
Err(err) => println!("failed to eval completer block: {}", err),
}
vec![]
None
}
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
@ -123,7 +123,8 @@ impl NuCompleter {
let offset = working_set.next_span_start();
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
let initial_line = line.to_string();
new_line.push(b'a');
let alias_total_offset: usize = alias_offset.iter().sum();
new_line.insert(alias_total_offset + pos, b'a');
let pos = offset + pos;
let config = self.engine_state.get_config();
@ -169,9 +170,10 @@ impl NuCompleter {
}
};
// Parses the prefix
// Parses the prefix. Completion should look up to the cursor position, not after.
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - (flat.0.start - span_offset));
let index = pos - (flat.0.start - span_offset);
prefix.drain(index..);
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -211,7 +213,11 @@ impl NuCompleter {
// We got no results for internal completion
// now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer {
return self.external_completion(block_id, spans, offset, new_span);
if let Some(external_result) =
self.external_completion(block_id, &spans, offset, new_span)
{
return external_result;
}
}
}
@ -338,6 +344,15 @@ impl NuCompleter {
return out;
}
// Try to complete using an external completer (if set)
if let Some(block_id) = config.external_completer {
if let Some(external_result) =
self.external_completion(block_id, &spans, offset, new_span)
{
return external_result;
}
}
// Check for file completion
let mut completer = FileCompletion::new(self.engine_state.clone());
out = self.process_completion(
@ -352,12 +367,6 @@ impl NuCompleter {
if !out.is_empty() {
return out;
}
// Try to complete using an exnternal compelter (if set)
if let Some(block_id) = config.external_completer {
return self
.external_completion(block_id, spans, offset, new_span);
}
}
};
}

View File

@ -822,6 +822,8 @@ fn event_from_record(
"ctrld" => ReedlineEvent::CtrlD,
"ctrlc" => ReedlineEvent::CtrlC,
"enter" => ReedlineEvent::Enter,
"submit" => ReedlineEvent::Submit,
"submitornewline" => ReedlineEvent::SubmitOrNewline,
"esc" | "escape" => ReedlineEvent::Esc,
"up" => ReedlineEvent::Up,
"down" => ReedlineEvent::Down,

View File

@ -11,7 +11,7 @@ use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
ast::PathMember,
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
@ -380,9 +380,13 @@ pub fn evaluate_repl(
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
let path = nu_path::expand_path_with(&s, &cwd);
let orig = s.clone();
let mut orig = s.clone();
if orig.starts_with('`') {
orig = trim_quotes_str(&orig).to_string()
}
let path = nu_path::expand_path_with(&orig, &cwd);
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd
@ -452,7 +456,7 @@ pub fn evaluate_repl(
span,
},
);
} else {
} else if !s.trim().is_empty() {
trace!("eval source: {}", s);
eval_source(
@ -578,7 +582,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
engine_state,
stack,
None,
"(date now) - ('05/10/2019' | into datetime)",
"(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
) {
Ok(Value::Duration { val, .. }) => format_duration(val),
_ => "".to_string(),
@ -595,13 +599,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
Our {}Documentation{} is located at {}http://nushell.sh{}
{}Tweet{} us at {}@nu_shell{}
{}Nushell{} has been around for:
It's been this long since {}Nushell{}'s first commit:
{}
{}You can disable this banner using the {}config nu{}{} command
to modify the config.nu file and setting show_banner to false.
let-env config {{
let-env config = {{
show_banner: false
...
}}{}

View File

@ -231,23 +231,18 @@ pub fn eval_source(
}
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => {
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
} else {
set_last_exit_code(stack, 0);
Ok(pipeline_data) => {
match pipeline_data.print(engine_state, stack, false, false) {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
Ok(exit_code) => {
set_last_exit_code(stack, exit_code);
}
} else {
set_last_exit_code(stack, 0);
}
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
return false;
}
// reset vt processing, aka ansi because illbehaved externals can break it

View File

@ -63,7 +63,7 @@ fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter
#[rstest]
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 9);
let suggestions = completer_strings.complete("my-c ", 4);
let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions);
}
@ -672,3 +672,63 @@ fn unknown_command_completion() {
match_suggestions(expected_paths, suggestions)
}
#[rstest]
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c | ls", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h | ls", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[test]
fn filecompletions_triggers_after_cursor() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("cp test_c", 3);
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions);
}

View File

@ -5,11 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.69.0"
version = "0.70.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-ansi-term = "0.46.0"
nu-json = { path = "../nu-json", version = "0.69.0" }
nu-table = { path = "../nu-table", version = "0.69.0" }
nu-json = { path = "../nu-json", version = "0.70.0" }
nu-table = { path = "../nu-table", version = "0.70.0" }
serde = { version="1.0.123", features=["derive"] }

View File

@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.69.0"
version = "0.70.0"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-glob = { path = "../nu-glob", version = "0.69.0" }
nu-json = { path = "../nu-json", version = "0.69.0" }
nu-parser = { path = "../nu-parser", version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-system = { path = "../nu-system", version = "0.69.0" }
nu-table = { path = "../nu-table", version = "0.69.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.69.0" }
nu-test-support = { path = "../nu-test-support", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
nu-glob = { path = "../nu-glob", version = "0.70.0" }
nu-json = { path = "../nu-json", version = "0.70.0" }
nu-parser = { path = "../nu-parser", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-system = { path = "../nu-system", version = "0.70.0" }
nu-table = { path = "../nu-table", version = "0.70.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.70.0" }
nu-test-support = { path = "../nu-test-support", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" }
num-format = { version = "0.4.3" }
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"
@ -88,11 +88,14 @@ unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.3.0", optional = true }
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0", features = ["diagnostics"] }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
[target.'cfg(windows)'.dependencies]
winreg = "0.10.1"
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
users = "0.11.0"

View File

@ -114,14 +114,21 @@ impl Command for SubCommand {
}),
},
Example {
description: "Convert string to a named duration",
description: "Convert string to the requested duration as a string",
example: "'7min' | into duration --convert sec",
result: Some(Value::String {
val: "420 sec".to_string(),
span,
}),
},
Example {
description: "Convert duration to the requested duration as a string",
example: "420sec | into duration --convert min",
result: Some(Value::String {
val: "7 min".to_string(),
span,
}),
},
]
}
}
@ -135,18 +142,20 @@ fn into_duration(
let head = call.head;
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let config = engine_state.get_config();
let float_precision = config.float_precision as usize;
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, &convert_to_unit, head)
action(&v, &convert_to_unit, float_precision, head)
} else {
let mut ret = v;
for path in &column_paths {
let d = convert_to_unit.clone();
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, &d, head)),
Box::new(move |old| action(old, &d, float_precision, head)),
);
if let Err(error) = r {
return Value::Error { error };
@ -166,127 +175,129 @@ fn convert_str_from_unit_to_unit(
to_unit: &str,
span: Span,
value_span: Span,
) -> Result<i64, ShellError> {
) -> Result<f64, ShellError> {
match (from_unit, to_unit) {
("ns", "ns") => Ok(val),
("ns", "us") => Ok(val / 1000),
("ns", "ms") => Ok(val / 1000 / 1000),
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
("ns", "ns") => Ok(val as f64),
("ns", "us") => Ok(val as f64 / 1000.0),
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ns", "dec") => {
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
}
("us", "ns") => Ok(val * 1000),
("us", "us") => Ok(val),
("us", "ms") => Ok(val / 1000),
("us", "sec") => Ok(val / 1000 / 1000),
("us", "min") => Ok(val / 1000 / 1000 / 60),
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
("us", "ns") => Ok(val as f64 * 1000.0),
("us", "us") => Ok(val as f64),
("us", "ms") => Ok(val as f64 / 1000.0),
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ms", "ns") => Ok(val * 1000 * 1000),
("ms", "us") => Ok(val * 1000),
("ms", "ms") => Ok(val),
("ms", "sec") => Ok(val / 1000),
("ms", "min") => Ok(val / 1000 / 60),
("ms", "hr") => Ok(val / 1000 / 60 / 60),
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
("ms", "us") => Ok(val as f64 * 1000.0),
("ms", "ms") => Ok(val as f64),
("ms", "sec") => Ok(val as f64 / 1000.0),
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("sec", "ns") => Ok(val * 1000 * 1000 * 1000),
("sec", "us") => Ok(val * 1000 * 1000),
("sec", "ms") => Ok(val * 1000),
("sec", "sec") => Ok(val),
("sec", "min") => Ok(val / 60),
("sec", "hr") => Ok(val / 60 / 60),
("sec", "day") => Ok(val / 60 / 60 / 24),
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
("sec", "ms") => Ok(val as f64 * 1000.0),
("sec", "sec") => Ok(val as f64),
("sec", "min") => Ok(val as f64 / 60.0),
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60),
("min", "us") => Ok(val * 1000 * 1000 * 60),
("min", "ms") => Ok(val * 1000 * 60),
("min", "sec") => Ok(val * 60),
("min", "min") => Ok(val),
("min", "hr") => Ok(val / 60),
("min", "day") => Ok(val / 60 / 24),
("min", "wk") => Ok(val / 60 / 24 / 7),
("min", "month") => Ok(val / 60 / 24 / 30),
("min", "yr") => Ok(val / 60 / 24 / 365),
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
("min", "sec") => Ok(val as f64 * 60.0),
("min", "min") => Ok(val as f64),
("min", "hr") => Ok(val as f64 / 60.0),
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
("hr", "ms") => Ok(val * 1000 * 60 * 60),
("hr", "sec") => Ok(val * 60 * 60),
("hr", "min") => Ok(val * 60),
("hr", "hr") => Ok(val),
("hr", "day") => Ok(val / 24),
("hr", "wk") => Ok(val / 24 / 7),
("hr", "month") => Ok(val / 24 / 30),
("hr", "yr") => Ok(val / 24 / 365),
("hr", "dec") => Ok(val / 10 / 24 / 365),
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
("hr", "min") => Ok(val as f64 * 60.0),
("hr", "hr") => Ok(val as f64),
("hr", "day") => Ok(val as f64 / 24.0),
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
("day", "sec") => Ok(val * 60 * 60 * 24),
("day", "min") => Ok(val * 60 * 24),
("day", "hr") => Ok(val * 24),
("day", "day") => Ok(val),
("day", "wk") => Ok(val / 7),
("day", "month") => Ok(val / 30),
("day", "yr") => Ok(val / 365),
("day", "dec") => Ok(val / 10 / 365),
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
("day", "hr") => Ok(val as f64 * 24.0),
("day", "day") => Ok(val as f64),
("day", "wk") => Ok(val as f64 / 7.0),
("day", "month") => Ok(val as f64 / 30.0),
("day", "yr") => Ok(val as f64 / 365.0),
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
("wk", "min") => Ok(val * 60 * 24 * 7),
("wk", "hr") => Ok(val * 24 * 7),
("wk", "day") => Ok(val * 7),
("wk", "wk") => Ok(val),
("wk", "month") => Ok(val / 4),
("wk", "yr") => Ok(val / 52),
("wk", "dec") => Ok(val / 10 / 52),
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
("wk", "day") => Ok(val as f64 * 7.0),
("wk", "wk") => Ok(val as f64),
("wk", "month") => Ok(val as f64 / 4.0),
("wk", "yr") => Ok(val as f64 / 52.0),
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
("month", "min") => Ok(val * 60 * 24 * 30),
("month", "hr") => Ok(val * 24 * 30),
("month", "day") => Ok(val * 30),
("month", "wk") => Ok(val * 4),
("month", "month") => Ok(val),
("month", "yr") => Ok(val / 12),
("month", "dec") => Ok(val / 10 / 12),
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
("month", "day") => Ok(val as f64 * 30.0),
("month", "wk") => Ok(val as f64 * 4.0),
("month", "month") => Ok(val as f64),
("month", "yr") => Ok(val as f64 / 12.0),
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
("yr", "min") => Ok(val * 60 * 24 * 365),
("yr", "hr") => Ok(val * 24 * 365),
("yr", "day") => Ok(val * 365),
("yr", "wk") => Ok(val * 52),
("yr", "month") => Ok(val * 12),
("yr", "yr") => Ok(val),
("yr", "dec") => Ok(val / 10),
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
("yr", "day") => Ok(val as f64 * 365.0),
("yr", "wk") => Ok(val as f64 * 52.0),
("yr", "month") => Ok(val as f64 * 12.0),
("yr", "yr") => Ok(val as f64),
("yr", "dec") => Ok(val as f64 / 10.0),
_ => Err(ShellError::CantConvertWithValue(
"string duration".to_string(),
@ -369,19 +380,41 @@ fn string_to_unit_duration(
))
}
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
fn action(
input: &Value,
convert_to_unit: &Option<Spanned<String>>,
float_precision: usize,
span: Span,
) -> Value {
match input {
Value::Duration {
val: _val_num,
span: _value_span,
val: val_num,
span: value_span,
} => {
if let Some(_to_unit) = convert_to_unit {
Value::Error {
error: ShellError::UnsupportedInput(
"Cannot convert from a Value::Duration right now. Try making it a string."
.into(),
span,
),
if let Some(to_unit) = convert_to_unit {
let from_unit = "ns";
let duration = *val_num;
match convert_str_from_unit_to_unit(
duration,
from_unit,
&to_unit.item,
span,
*value_span,
) {
Ok(d) => {
if d.fract() == 0.0 {
Value::String {
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
}
Err(e) => Value::Error { error: e },
}
} else {
input.clone()
@ -402,10 +435,19 @@ fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span)
span,
*value_span,
) {
Ok(d) => Value::String {
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
},
Ok(d) => {
if d.fract() == 0.0 {
Value::String {
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
}
Err(e) => Value::Error { error: e },
}
} else {
@ -450,7 +492,7 @@ mod test {
let expected = Value::Duration { val: 3, span };
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -464,7 +506,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -478,7 +520,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -492,7 +534,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -506,7 +548,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -520,7 +562,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -534,7 +576,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
@ -548,7 +590,7 @@ mod test {
};
let convert_duration = None;
let actual = action(&word, &convert_duration, span);
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
}

View File

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
Value,
};
#[derive(Clone)]
@ -102,9 +103,75 @@ impl Command for Do {
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
// collect stdout and stderr and check exit code.
// if exit code is not 0, return back ShellError.
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
Ok(PipelineData::ExternalStream {
stdout,
stderr,
exit_code,
span,
metadata,
}) => {
// collect all output first.
let mut stderr_ctrlc = None;
let stderr_msg = match stderr {
None => "".to_string(),
Some(stderr_stream) => {
stderr_ctrlc = stderr_stream.ctrlc.clone();
stderr_stream.into_string().map(|s| s.item)?
}
};
let mut stdout_ctrlc = None;
let stdout_msg = match stdout {
None => "".to_string(),
Some(stdout_stream) => {
stdout_ctrlc = stdout_stream.ctrlc.clone();
stdout_stream.into_string().map(|s| s.item)?
}
};
let mut exit_code_ctrlc = None;
let exit_code: Vec<Value> = match exit_code {
None => vec![],
Some(exit_code_stream) => {
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
exit_code_stream.into_iter().collect()
}
};
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
return Err(ShellError::ExternalCommand(
"External command runs to failed".to_string(),
stderr_msg,
span,
));
}
}
// construct pipeline data to our caller
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
stdout_ctrlc,
span,
)),
stderr: Some(RawStream::new(
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
stderr_ctrlc,
span,
)),
exit_code: Some(ListStream::from_stream(
exit_code.into_iter(),
exit_code_ctrlc,
)),
span,
metadata,
})
}
Ok(other) => Ok(other),
Err(e) => Err(e),
}
} else {
result

View File

@ -59,4 +59,8 @@ impl Command for ExportCommand {
}),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
}

View File

@ -51,4 +51,8 @@ impl Command for ExportAlias {
result: None,
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["aka", "abbr", "module"]
}
}

View File

@ -55,4 +55,8 @@ impl Command for ExportDef {
}),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
}

View File

@ -81,4 +81,8 @@ export def-env cd_with_fallback [arg = ""] {
}),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
}

View File

@ -47,4 +47,8 @@ impl Command for ExportExtern {
result: None,
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["signature", "module", "declare"]
}
}

View File

@ -53,4 +53,8 @@ impl Command for ExportUse {
}),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["reexport", "import", "module"]
}
}

View File

@ -13,7 +13,7 @@ mod last;
mod list;
mod melt;
mod open;
mod query_dfr;
mod query_df;
mod rename;
mod sample;
mod shape;
@ -45,7 +45,7 @@ pub use last::LastDF;
pub use list::ListDF;
pub use melt::MeltDF;
pub use open::OpenDataFrame;
pub use query_dfr::QueryDfr;
pub use query_df::QueryDf;
pub use rename::RenameDF;
pub use sample::SampleDF;
pub use shape::ShapeDF;
@ -87,7 +87,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
ListDF,
MeltDF,
OpenDataFrame,
QueryDfr,
QueryDf,
RenameDF,
SampleDF,
ShapeDF,

View File

@ -14,11 +14,11 @@ use nu_protocol::{
// https://github.com/pola-rs/polars/tree/master/polars-sql
#[derive(Clone)]
pub struct QueryDfr;
pub struct QueryDf;
impl Command for QueryDfr {
impl Command for QueryDf {
fn name(&self) -> &str {
"query dfr"
"query df"
}
fn usage(&self) -> &str {
@ -40,7 +40,7 @@ impl Command for QueryDfr {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Query dataframe using SQL",
example: "[[a b]; [1 2] [3 4]] | into df | query dfr 'select a from df'",
example: "[[a b]; [1 2] [3 4]] | into df | query df 'select a from df'",
result: Some(
NuDataFrame::try_from_columns(vec![Column::new(
"a".to_string(),
@ -101,6 +101,6 @@ mod test {
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(QueryDfr {})])
test_dataframe(vec![Box::new(QueryDf {})])
}
}

View File

@ -19,6 +19,10 @@ impl Command for ArgMax {
"Return index for max value in series"
}
fn search_terms(&self) -> Vec<&str> {
vec!["argmax", "maximum", "most", "largest", "greatest"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgMin {
"Return index for min value in series"
}
fn search_terms(&self) -> Vec<&str> {
vec!["argmin", "minimum", "least", "smallest", "lowest"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgSort {
"Returns indexes for a sorted series"
}
fn search_terms(&self) -> Vec<&str> {
vec!["argsort", "order", "arrange"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.switch("reverse", "reverse order", Some('r'))

View File

@ -19,6 +19,10 @@ impl Command for ArgTrue {
"Returns indexes where values are true"
}
fn search_terms(&self) -> Vec<&str> {
vec!["argtrue", "truth", "boolean-true"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ArgUnique {
"Returns indexes for unique values"
}
fn search_terms(&self) -> Vec<&str> {
vec!["argunique", "distinct", "noduplicate", "unrepeated"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Custom("dataframe".into()))

View File

@ -19,6 +19,10 @@ impl Command for ToUpperCase {
"Uppercase the strings in the column"
}
fn search_terms(&self) -> Vec<&str> {
vec!["capitalize, caps, capital"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_type(Type::Custom("dataframe".into()))

View File

@ -158,12 +158,17 @@ pub fn create_default_context() -> EngineState {
bind_command! {
Benchmark,
Complete,
Exec,
External,
NuCheck,
Sys,
};
#[cfg(unix)]
bind_command! { Exec }
#[cfg(windows)]
bind_command! { RegistryQuery }
#[cfg(any(
target_os = "android",
target_os = "linux",

View File

@ -37,7 +37,7 @@ impl Command for ConfigEnv {
&self,
engine_state: &EngineState,
stack: &mut Stack,
_call: &Call,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let env_vars_str = env_to_strings(engine_state, stack)?;
@ -59,7 +59,7 @@ impl Command for ConfigEnv {
let name = Spanned {
item: get_editor(engine_state, stack)?,
span: Span { start: 0, end: 0 },
span: call.head,
};
let args = vec![Spanned {
@ -76,6 +76,6 @@ impl Command for ConfigEnv {
env_vars: env_vars_str,
};
command.run_with_input(engine_state, stack, input)
command.run_with_input(engine_state, stack, input, true)
}
}

View File

@ -37,7 +37,7 @@ impl Command for ConfigNu {
&self,
engine_state: &EngineState,
stack: &mut Stack,
_call: &Call,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let env_vars_str = env_to_strings(engine_state, stack)?;
@ -59,7 +59,7 @@ impl Command for ConfigNu {
let name = Spanned {
item: get_editor(engine_state, stack)?,
span: Span { start: 0, end: 0 },
span: call.head,
};
let args = vec![Spanned {
@ -76,6 +76,6 @@ impl Command for ConfigNu {
env_vars: env_vars_str,
};
command.run_with_input(engine_state, stack, input)
command.run_with_input(engine_state, stack, input, true)
}
}

View File

@ -99,6 +99,16 @@ impl Command for Glob {
let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let depth = call.get_flag(engine_state, stack, "depth")?;
if glob_pattern.item.is_empty() {
return Err(ShellError::GenericError(
"glob pattern must not be empty".to_string(),
"".to_string(),
Some(glob_pattern.span),
Some("add characters to the glob pattern".to_string()),
Vec::new(),
));
}
let folder_depth = if let Some(depth) = depth {
depth
} else {

View File

@ -491,7 +491,10 @@ pub(crate) fn dir_entry_dict(
span,
});
} else {
vals.push(Value::nothing(span))
vals.push(Value::Int {
val: md.uid() as i64,
span,
})
}
cols.push("group".into());
@ -501,7 +504,10 @@ pub(crate) fn dir_entry_dict(
span,
});
} else {
vals.push(Value::nothing(span))
vals.push(Value::Int {
val: md.gid() as i64,
span,
})
}
}
}

View File

@ -1,7 +1,7 @@
use std::fs::OpenOptions;
use std::path::Path;
use chrono::{DateTime, Datelike, Local};
use chrono::{DateTime, Local};
use filetime::FileTime;
use nu_engine::CallExt;
@ -9,13 +9,6 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
use crate::parse_date_from_string;
enum AddYear {
Full,
FirstDigits,
}
#[derive(Clone)]
pub struct Touch;
@ -35,18 +28,6 @@ impl Command for Touch {
SyntaxShape::Filepath,
"the path of the file you want to create",
)
.named(
"timestamp",
SyntaxShape::String,
"change the file or directory time to a timestamp. Format: [[CC]YY]MMDDhhmm[.ss]\n\n If neither YY or CC is given, the current year will be assumed. If YY is specified, but CC is not, CC will be derived as follows:\n \tIf YY is between [69, 99], CC is 19\n \tIf YY is between [00, 68], CC is 20\n Note: It is expected that in a future version of this standard the default century inferred from a 2-digit year will change",
Some('t'),
)
.named(
"date",
SyntaxShape::String,
"change the file or directory time to a date",
Some('d'),
)
.named(
"reference",
SyntaxShape::String,
@ -85,8 +66,6 @@ impl Command for Touch {
) -> Result<PipelineData, ShellError> {
let mut change_mtime: bool = call.has_flag("modified");
let mut change_atime: bool = call.has_flag("access");
let use_stamp: bool = call.has_flag("timestamp");
let use_date: bool = call.has_flag("date");
let use_reference: bool = call.has_flag("reference");
let no_create: bool = call.has_flag("no-create");
let target: String = call.req(engine_state, stack, 0)?;
@ -105,111 +84,6 @@ impl Command for Touch {
date = Some(Local::now());
}
if use_stamp || use_date {
let (val, span) = if use_stamp {
let stamp: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "timestamp")?;
let (stamp, span) = match stamp {
Some(stamp) => (stamp.item, stamp.span),
None => {
return Err(ShellError::MissingParameter(
"timestamp".to_string(),
call.head,
));
}
};
// Checks for the seconds stamp and removes the '.' delimiter if any
let (val, has_sec): (String, bool) = match stamp.split_once('.') {
Some((dtime, sec)) => match sec.parse::<u8>() {
Ok(sec) if sec < 60 => (format!("{}{}", dtime, sec), true),
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
},
None => (stamp.to_string(), false),
};
let size = val.len();
// Each stamp is a 2 digit number and the whole stamp must not be less than 4 or greater than 7 pairs
if (size % 2 != 0 || !(8..=14).contains(&size)) || val.parse::<u64>().is_err() {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
));
}
let add_year: Option<AddYear> = if has_sec {
match size {
10 => Some(AddYear::Full),
12 => Some(AddYear::FirstDigits),
14 => None,
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
}
} else {
match size {
8 => Some(AddYear::Full),
10 => Some(AddYear::FirstDigits),
12 => None,
_ => {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
))
}
}
};
if let Some(add_year) = add_year {
let year = Local::now().year();
match add_year {
AddYear::Full => (format!("{}{}", year, val), span),
AddYear::FirstDigits => {
// Compliance with the Unix version of touch
let yy = val[0..2]
.parse::<u8>()
.expect("should be a valid 2 digit number");
let mut year = 20;
if (69..=99).contains(&yy) {
year = 19;
}
(format!("{}{}", year, val), span)
}
}
} else {
(val, span)
}
} else {
let date_string: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "date")?;
match date_string {
Some(date_string) => (date_string.item, date_string.span),
None => {
return Err(ShellError::MissingParameter("date".to_string(), call.head));
}
}
};
date = if let Ok(parsed_date) = parse_date_from_string(&val, span) {
Some(parsed_date.into())
} else {
let flag = if use_stamp { "timestamp" } else { "date" };
return Err(ShellError::UnsupportedInput(
format!("input has an invalid {}", flag),
span,
));
};
}
if use_reference {
let reference: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "reference")?;
@ -336,11 +210,6 @@ impl Command for Touch {
example: "touch -m fixture.json",
result: None,
},
Example {
description: "Creates files d and e and set its last modified time to a timestamp",
example: "touch -m -t 201908241230.30 d e",
result: None,
},
Example {
description: "Changes the last modified time of files a, b and c to a date",
example: r#"touch -m -d "yesterday" a b c"#,
@ -356,11 +225,6 @@ impl Command for Touch {
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
result: None,
},
Example {
description: "Changes both last modified and accessed time of a, b and c to a timestamp only if they exist",
example: r#"touch -c -t 201908241230.30 a b c"#,
result: None,
},
]
}
}

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{eval_block, redirect_env, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -20,6 +20,11 @@ impl Command for Collect {
SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
"the block to run once the stream is collected",
)
.switch(
"keep-env",
"let the block affect environment variables",
None,
)
.category(Category::Filters)
}
@ -37,26 +42,43 @@ impl Command for Collect {
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id).clone();
let mut stack = stack.captures_to_stack(&capture_block.captures);
let mut stack_captures = stack.captures_to_stack(&capture_block.captures);
let metadata = input.metadata();
let input: Value = input.into_value(call.head);
let mut saved_positional = None;
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, input.clone());
stack_captures.add_var(*var_id, input.clone());
saved_positional = Some(*var_id);
}
}
eval_block(
let result = eval_block(
engine_state,
&mut stack,
&mut stack_captures,
&block,
input.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
)
.map(|x| x.set_metadata(metadata))
.map(|x| x.set_metadata(metadata));
if call.has_flag("keep-env") {
redirect_env(engine_state, stack, &stack_captures);
// for when we support `data | let x = $in;`
// remove the variables added earlier
for var_id in capture_block.captures.keys() {
stack_captures.vars.remove(var_id);
}
if let Some(u) = saved_positional {
stack_captures.vars.remove(&u);
}
// add any new variables to the stack
stack.vars.extend(stack_captures.vars);
}
result
}
fn examples(&self) -> Vec<Example> {

View File

@ -146,7 +146,7 @@ fn first_helper(
}
}
_ => {
if rows_desired == 1 {
if rows_desired == 1 && rows.is_none() {
match input_peek.next() {
Some(val) => Ok(val.into_pipeline_data()),
None => Err(ShellError::AccessBeyondEndOfStream(head)),

View File

@ -11,6 +11,10 @@ impl Command for Roll {
"roll"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move"]
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Filters)
}

View File

@ -16,6 +16,10 @@ impl Command for RollDown {
"roll down"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "row"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))

View File

@ -16,6 +16,10 @@ impl Command for RollLeft {
"roll left"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "column"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(

View File

@ -16,6 +16,10 @@ impl Command for RollRight {
"roll right"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "column"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named(

View File

@ -16,6 +16,10 @@ impl Command for RollUp {
"roll up"
}
fn search_terms(&self) -> Vec<&str> {
vec!["rotate", "shift", "move", "row"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))

View File

@ -23,6 +23,11 @@ impl Command for Window {
"the number of rows to slide over between windows",
Some('s'),
)
.switch(
"remainder",
"yield last chunks even if they have fewer elements than size",
Some('r'),
)
.category(Category::Filters)
}
@ -115,6 +120,39 @@ impl Command for Window {
},
];
let stream_test_3 = vec![
Value::List {
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::Int {
val: 2,
span: Span::test_data(),
},
Value::Int {
val: 3,
span: Span::test_data(),
},
],
span: Span::test_data(),
},
Value::List {
vals: vec![
Value::Int {
val: 4,
span: Span::test_data(),
},
Value::Int {
val: 5,
span: Span::test_data(),
},
],
span: Span::test_data(),
},
];
vec![
Example {
example: "echo [1 2 3 4] | window 2",
@ -132,6 +170,14 @@ impl Command for Window {
span: Span::test_data(),
}),
},
Example {
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
result: Some(Value::List {
vals: stream_test_3,
span: Span::test_data(),
}),
},
]
}
@ -146,6 +192,7 @@ impl Command for Window {
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata();
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
let remainder = call.has_flag("remainder");
let stride = stride.unwrap_or(1);
@ -155,8 +202,9 @@ impl Command for Window {
group_size: group_size.item,
input: Box::new(input.into_iter()),
span: call.head,
previous: vec![],
previous: None,
stride,
remainder,
};
Ok(each_group_iterator
@ -169,15 +217,23 @@ struct EachWindowIterator {
group_size: usize,
input: Box<dyn Iterator<Item = Value> + Send>,
span: Span,
previous: Vec<Value>,
previous: Option<Vec<Value>>,
stride: usize,
remainder: bool,
}
impl Iterator for EachWindowIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
let mut group = self.previous.clone();
let mut group = self.previous.take().unwrap_or_else(|| {
let mut vec = Vec::new();
// We default to a Vec of capacity size + stride as striding pushes n extra elements to the end
vec.try_reserve(self.group_size + self.stride).ok();
vec
});
let mut current_count = 0;
if group.is_empty() {
@ -193,7 +249,13 @@ impl Iterator for EachWindowIterator {
break;
}
}
None => return None,
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
} else {
@ -211,23 +273,31 @@ impl Iterator for EachWindowIterator {
break;
}
}
None => return None,
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
for _ in 0..current_count {
let _ = group.remove(0);
}
// We now have elements + stride in our group, and need to
// drop the skipped elements. Drain to preserve allocation and capacity
// Dropping this iterator consumes it.
group.drain(..self.stride.min(group.len()));
}
if group.is_empty() || current_count == 0 {
if group.is_empty() {
return None;
}
self.previous = group.clone();
let return_group = group.clone();
self.previous = Some(group);
Some(Value::List {
vals: group,
vals: return_group,
span: self.span,
})
}

View File

@ -173,12 +173,11 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
fn value_to_string_without_quotes(v: &Value, span: Span) -> Result<String, ShellError> {
match v {
Value::String { val, .. } => Ok({
let mut quoted = escape_quote_string(val);
if !needs_quotes(val) {
quoted.remove(0);
quoted.pop();
if needs_quotes(val) {
escape_quote_string(val)
} else {
val.clone()
}
quoted
}),
_ => value_to_string(v, span),
}
@ -209,6 +208,7 @@ fn needs_quotes(string: &str) -> bool {
|| string.contains('\t')
|| string.contains('\n')
|| string.contains('\r')
|| string.contains('\"')
}
#[cfg(test)]

View File

@ -66,10 +66,6 @@ impl Command for ToText {
},
]
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert"]
}
}
fn local_into_string(value: Value, separator: &str, config: &Config) -> String {

View File

@ -149,7 +149,7 @@ impl Command for Input {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get input from the user, and assign to a variable",
example: "let user-input = (input)",
example: "let user_input = (input)",
result: None,
}]
}

View File

@ -12,35 +12,28 @@ impl Command for TermSize {
}
fn usage(&self) -> &str {
"Returns the terminal size"
"Returns a record containing the number of columns (width) and rows (height) of the terminal"
}
fn signature(&self) -> Signature {
Signature::build("term size")
.switch(
"columns",
"Report only the width of the terminal",
Some('c'),
)
.switch("rows", "Report only the height of the terminal", Some('r'))
.category(Category::Platform)
Signature::build("term size").category(Category::Platform)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the width height of the terminal",
description: "Return the columns (width) and rows (height) of the terminal",
example: "term size",
result: None,
},
Example {
description: "Return the width (columns) of the terminal",
example: "term size -c",
description: "Return the columns (width) of the terminal",
example: "(term size).columns",
result: None,
},
Example {
description: "Return the height (rows) of the terminal",
example: "term size -r",
description: "Return the rows (height) of the terminal",
example: "(term size).rows",
result: None,
},
]
@ -54,60 +47,26 @@ impl Command for TermSize {
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
let wide = call.has_flag("columns");
let tall = call.has_flag("rows");
let (cols, rows) = match terminal_size() {
Some((w, h)) => (Width(w.0), Height(h.0)),
None => (Width(0), Height(0)),
};
Ok((match (wide, tall) {
(true, false) => Value::Record {
cols: vec!["columns".into()],
vals: vec![Value::Int {
Ok(Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
}],
span: head,
},
(true, true) => Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
},
],
span: head,
},
(false, true) => Value::Record {
cols: vec!["rows".into()],
vals: vec![Value::Int {
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
}],
span: head,
},
(false, false) => Value::Record {
cols: vec!["columns".into(), "rows".into()],
vals: vec![
Value::Int {
val: cols.0 as i64,
span: Span::test_data(),
},
Value::Int {
val: rows.0 as i64,
span: Span::test_data(),
},
],
span: head,
},
})
},
],
span: head,
}
.into_pipeline_data())
}
}

View File

@ -9,8 +9,8 @@ use std::sync::Arc;
struct Arguments {
all: bool,
find: String,
replace: String,
find: Spanned<String>,
replace: Spanned<String>,
column_paths: Vec<CellPath>,
literal_replace: bool,
no_regex: bool,
@ -168,8 +168,8 @@ fn operate(
let options = Arc::new(Arguments {
all: call.has_flag("all"),
find: find.item,
replace: replace.item,
find,
replace,
column_paths: call.rest(engine_state, stack, 2)?,
literal_replace,
no_regex,
@ -214,23 +214,23 @@ fn action(
) -> Value {
match input {
Value::String { val, .. } => {
let FindReplace(find, replacement) = FindReplace(find, replace);
let FindReplace(find_str, replace_str) = FindReplace(&find.item, &replace.item);
if *no_regex {
// just use regular string replacement vs regular expressions
if *all {
Value::String {
val: val.replace(find, replacement),
val: val.replace(find_str, replace_str),
span: head,
}
} else {
Value::String {
val: val.replacen(find, replacement, 1),
val: val.replacen(find_str, replace_str, 1),
span: head,
}
}
} else {
// use regular expressions to replace strings
let regex = Regex::new(find);
let regex = Regex::new(find_str);
match regex {
Ok(re) => {
@ -238,9 +238,9 @@ fn action(
Value::String {
val: {
if *literal_replace {
re.replace_all(val, NoExpand(replacement)).to_string()
re.replace_all(val, NoExpand(replace_str)).to_string()
} else {
re.replace_all(val, replacement).to_string()
re.replace_all(val, replace_str).to_string()
}
},
span: head,
@ -249,18 +249,17 @@ fn action(
Value::String {
val: {
if *literal_replace {
re.replace(val, NoExpand(replacement)).to_string()
re.replace(val, NoExpand(replace_str)).to_string()
} else {
re.replace(val, replacement).to_string()
re.replace(val, replace_str).to_string()
}
},
span: head,
}
}
}
Err(_) => Value::String {
val: val.to_string(),
span: head,
Err(e) => Value::Error {
error: ShellError::UnsupportedInput(format!("{e}"), find.span),
},
}
}
@ -282,6 +281,13 @@ mod tests {
use super::*;
use super::{action, Arguments, SubCommand};
fn test_spanned_string(val: &str) -> Spanned<String> {
Spanned {
item: String::from(val),
span: Span::test_data(),
}
}
#[test]
fn test_examples() {
use crate::test_examples;
@ -297,8 +303,8 @@ mod tests {
};
let options = Arguments {
find: String::from("Cargo.(.+)"),
replace: String::from("Carga.$1"),
find: test_spanned_string("Cargo.(.+)"),
replace: test_spanned_string("Carga.$1"),
column_paths: vec![],
literal_replace: false,
all: false,

View File

@ -37,6 +37,35 @@ impl Command for Complete {
let mut cols = vec![];
let mut vals = vec![];
// use a thread to receive stderr message.
// Or we may get a deadlock if child process sends out too much bytes to stdout.
//
// For example: in normal linux system, stdout pipe's limit is 65535 bytes.
// if child process sends out 65536 bytes, the process will be hanged because no consumer
// consumes the first 65535 bytes
// So we need a thread to receive stderr message, then the current thread can continue to consume
// stdout messages.
let stderr_handler = stderr.map(|stderr| {
let stderr_span = stderr.span;
(
std::thread::spawn(move || {
let stderr = stderr.into_bytes()?;
if let Ok(st) = String::from_utf8(stderr.item.clone()) {
Ok::<_, ShellError>(Value::String {
val: st,
span: stderr.span,
})
} else {
Ok::<_, ShellError>(Value::Binary {
val: stderr.item,
span: stderr.span,
})
}
}),
stderr_span,
)
});
if let Some(stdout) = stdout {
cols.push("stdout".to_string());
let stdout = stdout.into_bytes()?;
@ -53,21 +82,17 @@ impl Command for Complete {
}
}
if let Some(stderr) = stderr {
if let Some((handler, stderr_span)) = stderr_handler {
cols.push("stderr".to_string());
let stderr = stderr.into_bytes()?;
if let Ok(st) = String::from_utf8(stderr.item.clone()) {
vals.push(Value::String {
val: st,
span: stderr.span,
})
} else {
vals.push(Value::Binary {
val: stderr.item,
span: stderr.span,
})
};
}
let res = handler.join().map_err(|err| {
ShellError::ExternalCommand(
"Fail to receive external commands stderr message".to_string(),
format!("{err:?}"),
stderr_span,
)
})??;
vals.push(res)
};
if let Some(exit_code) = exit_code {
let mut v: Vec<_> = exit_code.collect();

View File

@ -1,8 +1,11 @@
use super::run_external::ExternalCommand;
use nu_engine::{current_dir, env_to_strings, CallExt};
use nu_protocol::{
ast::Call,
ast::{Call, Expr},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
};
use std::os::unix::process::CommandExt;
#[derive(Clone)]
pub struct Exec;
@ -57,19 +60,11 @@ impl Command for Exec {
}
}
#[cfg(unix)]
fn exec(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
use std::os::unix::process::CommandExt;
use super::run_external::ExternalCommand;
use nu_engine::{current_dir, env_to_strings, CallExt};
use nu_protocol::ast::Expr;
use nu_protocol::Spanned;
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let name_span = name.span;
@ -113,18 +108,3 @@ fn exec(
Vec::new(),
))
}
#[cfg(not(unix))]
fn exec(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Err(ShellError::GenericError(
"Error on exec".to_string(),
"exec is not supported on your platform".to_string(),
Some(call.head),
None,
Vec::new(),
))
}

View File

@ -1,5 +1,6 @@
mod benchmark;
mod complete;
#[cfg(unix)]
mod exec;
mod nu_check;
#[cfg(any(
@ -9,12 +10,15 @@ mod nu_check;
target_os = "windows"
))]
mod ps;
#[cfg(windows)]
mod registry_query;
mod run_external;
mod sys;
mod which_;
pub use benchmark::Benchmark;
pub use complete::Complete;
#[cfg(unix)]
pub use exec::Exec;
pub use nu_check::NuCheck;
#[cfg(any(
@ -24,6 +28,8 @@ pub use nu_check::NuCheck;
target_os = "windows"
))]
pub use ps::Ps;
#[cfg(windows)]
pub use registry_query::RegistryQuery;
pub use run_external::{External, ExternalCommand};
pub use sys::Sys;
pub use which_::Which;

View File

@ -0,0 +1,273 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Value,
};
use winreg::{enums::*, RegKey};
#[derive(Clone)]
pub struct RegistryQuery;
struct RegistryQueryArgs {
hkcr: bool,
hkcu: bool,
hklm: bool,
hku: bool,
hkpd: bool,
hkpt: bool,
hkpnls: bool,
hkcc: bool,
hkdd: bool,
hkculs: bool,
key: String,
}
impl Command for RegistryQuery {
fn name(&self) -> &str {
"registry query"
}
fn signature(&self) -> Signature {
Signature::build("registry query")
.switch("hkcr", "query the hkey_classes_root hive", None)
.switch("hkcu", "query the hkey_current_user hive", None)
.switch("hklm", "query the hkey_local_machine hive", None)
.switch("hku", "query the hkey_users hive", None)
.switch("hkpd", "query the hkey_performance_data hive", None)
.switch("hkpt", "query the hkey_performance_text hive", None)
.switch("hkpnls", "query the hkey_performance_nls_text hive", None)
.switch("hkcc", "query the hkey_current_config hive", None)
.switch("hkdd", "query the hkey_dyn_data hive", None)
.switch(
"hkculs",
"query the hkey_current_user_local_settings hive",
None,
)
.required("key", SyntaxShape::String, "registry key to query")
.optional(
"value",
SyntaxShape::String,
"optionally supply a registry value to query",
)
.category(Category::System)
}
fn usage(&self) -> &str {
"Query the Windows registry."
}
fn extra_usage(&self) -> &str {
"Currently supported only on Windows systems."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
registry_query(engine_state, stack, call)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Query the HKEY_CURRENT_USER hive",
example: "registry query --hkcu environment",
result: None,
},
Example {
description: "Query the HKEY_LOCAL_MACHINE hive",
example: r"registry query --hklm 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'",
result: None,
},
]
}
}
fn registry_query(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let registry_key: Spanned<String> = call.req(engine_state, stack, 0)?;
let registry_key_span = &registry_key.clone().span;
let registry_value: Option<Spanned<String>> = call.opt(engine_state, stack, 1)?;
let reg_params = RegistryQueryArgs {
hkcr: call.has_flag("hkcr"),
hkcu: call.has_flag("hkcu"),
hklm: call.has_flag("hklm"),
hku: call.has_flag("hku"),
hkpd: call.has_flag("hkpd"),
hkpt: call.has_flag("hkpt"),
hkpnls: call.has_flag("hkpnls"),
hkcc: call.has_flag("hkcc"),
hkdd: call.has_flag("hkdd"),
hkculs: call.has_flag("hkculs"),
key: registry_key.item,
};
let reg_key = get_reg_key(reg_params)?;
if registry_value.is_none() {
let mut reg_values = vec![];
for (name, val) in reg_key.enum_values().flatten() {
let (nu_value, reg_type) = reg_value_to_nu_value(val);
reg_values.push(Value::Record {
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
vals: vec![
Value::string(name, Span::test_data()),
nu_value,
Value::string(format!("{:?}", reg_type), Span::test_data()),
],
span: *registry_key_span,
})
}
Ok(reg_values.into_pipeline_data(engine_state.ctrlc.clone()))
} else {
match registry_value {
Some(value) => {
let reg_value = reg_key.get_raw_value(value.item.as_str());
match reg_value {
Ok(val) => {
let (nu_value, reg_type) = reg_value_to_nu_value(val);
Ok(Value::Record {
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
vals: vec![
Value::string(value.item, Span::test_data()),
nu_value,
Value::string(format!("{:?}", reg_type), Span::test_data()),
],
span: value.span,
}
.into_pipeline_data())
}
Err(_) => Ok(Value::Error {
error: ShellError::GenericError(
"Unable to find registry key/value".to_string(),
format!("Registry value: {} was not found", value.item),
Some(value.span),
None,
Vec::new(),
),
}
.into_pipeline_data()),
}
}
None => Ok(Value::nothing(Span::test_data()).into_pipeline_data()),
}
}
}
fn get_reg_key(reg_params: RegistryQueryArgs) -> Result<RegKey, ShellError> {
let mut key_count = 0;
let registry_key = if reg_params.hkcr {
key_count += 1;
RegKey::predef(HKEY_CLASSES_ROOT).open_subkey(reg_params.key)?
} else if reg_params.hkcu {
key_count += 1;
RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)?
} else if reg_params.hklm {
key_count += 1;
RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey(reg_params.key)?
} else if reg_params.hku {
key_count += 1;
RegKey::predef(HKEY_USERS).open_subkey(reg_params.key)?
} else if reg_params.hkpd {
key_count += 1;
RegKey::predef(HKEY_PERFORMANCE_DATA).open_subkey(reg_params.key)?
} else if reg_params.hkpt {
key_count += 1;
RegKey::predef(HKEY_PERFORMANCE_TEXT).open_subkey(reg_params.key)?
} else if reg_params.hkpnls {
key_count += 1;
RegKey::predef(HKEY_PERFORMANCE_NLSTEXT).open_subkey(reg_params.key)?
} else if reg_params.hkcc {
key_count += 1;
RegKey::predef(HKEY_CURRENT_CONFIG).open_subkey(reg_params.key)?
} else if reg_params.hkdd {
key_count += 1;
RegKey::predef(HKEY_DYN_DATA).open_subkey(reg_params.key)?
} else if reg_params.hkculs {
key_count += 1;
RegKey::predef(HKEY_CURRENT_USER_LOCAL_SETTINGS).open_subkey(reg_params.key)?
} else {
RegKey::predef(HKEY_CURRENT_USER).open_subkey(reg_params.key)?
};
if key_count > 1 {
return Err(ShellError::GenericError(
"Only one registry key can be specified".into(),
"Only one registry key can be specified".into(),
Some(Span::test_data()),
None,
Vec::new(),
));
}
Ok(registry_key)
}
fn reg_value_to_nu_value(
reg_value: winreg::RegValue,
) -> (nu_protocol::Value, winreg::enums::RegType) {
match reg_value.vtype {
REG_NONE => (Value::nothing(Span::test_data()), reg_value.vtype),
REG_SZ => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_EXPAND_SZ => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_BINARY => (
Value::binary(reg_value.bytes, Span::test_data()),
reg_value.vtype,
),
REG_DWORD => (
Value::int(
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
Span::test_data(),
),
reg_value.vtype,
),
REG_DWORD_BIG_ENDIAN => (
Value::int(
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
Span::test_data(),
),
reg_value.vtype,
),
REG_LINK => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_MULTI_SZ => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_RESOURCE_LIST => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_FULL_RESOURCE_DESCRIPTOR => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_RESOURCE_REQUIREMENTS_LIST => (
Value::string(reg_value.to_string(), Span::test_data()),
reg_value.vtype,
),
REG_QWORD => (
Value::int(
unsafe { *(reg_value.bytes.as_ptr() as *const u32) } as i64,
Span::test_data(),
),
reg_value.vtype,
),
}
}

View File

@ -7,13 +7,15 @@ use nu_protocol::did_you_mean;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value};
use nu_protocol::{Category, Example, ListStream, PipelineData, RawStream, Span, Spanned};
use nu_system::ForegroundProcess;
use pathdiff::diff_paths;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command as CommandSys, Stdio};
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, SyncSender};
use std::sync::Arc;
const OUTPUT_BUFFER_SIZE: usize = 1024;
const OUTPUT_BUFFERS_IN_FLIGHT: usize = 3;
@ -32,9 +34,9 @@ impl Command for External {
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name())
.switch("redirect-stdout", "redirect-stdout", None)
.switch("redirect-stderr", "redirect-stderr", None)
.required("command", SyntaxShape::Any, "external comamdn to run")
.switch("redirect-stdout", "redirect stdout to the pipeline", None)
.switch("redirect-stderr", "redirect stderr to the pipeline", None)
.required("command", SyntaxShape::Any, "external command to run")
.rest("args", SyntaxShape::Any, "arguments for external command")
.category(Category::System)
}
@ -108,15 +110,22 @@ impl Command for External {
redirect_stderr,
env_vars: env_vars_str,
};
command.run_with_input(engine_state, stack, input)
command.run_with_input(engine_state, stack, input, false)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Run an external command",
example: r#"run-external "echo" "-n" "hello""#,
result: None,
}]
vec![
Example {
description: "Run an external command",
example: r#"run-external "echo" "-n" "hello""#,
result: None,
},
Example {
description: "Redirect stdout from an external command into the pipeline",
example: r#"run-external --redirect-stdout "echo" "-n" "hello" | split chars"#,
result: None,
},
]
}
}
@ -136,12 +145,16 @@ impl ExternalCommand {
engine_state: &EngineState,
stack: &mut Stack,
input: PipelineData,
reconfirm_command_name: bool,
) -> Result<PipelineData, ShellError> {
let head = self.name.span;
let ctrlc = engine_state.ctrlc.clone();
let mut process = self.create_process(&input, false, head)?;
let mut fg_process = ForegroundProcess::new(
self.create_process(&input, false, head)?,
engine_state.pipeline_externals_state.clone(),
);
// mut is used in the windows branch only, suppress warning on other platforms
#[allow(unused_mut)]
let mut child;
@ -156,8 +169,7 @@ impl ExternalCommand {
// fails to be run as a normal executable:
// 1. "shell out" to cmd.exe if the command is a known cmd.exe internal command
// 2. Otherwise, use `which-rs` to look for batch files etc. then run those in cmd.exe
match process.spawn() {
match fg_process.spawn() {
Err(err) => {
// set the default value, maybe we'll override it later
child = Err(err);
@ -174,7 +186,10 @@ impl ExternalCommand {
.any(|&cmd| command_name_upper == cmd);
if looks_like_cmd_internal {
let mut cmd_process = self.create_process(&input, true, head)?;
let mut cmd_process = ForegroundProcess::new(
self.create_process(&input, true, head)?,
engine_state.pipeline_externals_state.clone(),
);
child = cmd_process.spawn();
} else {
#[cfg(feature = "which-support")]
@ -202,8 +217,11 @@ impl ExternalCommand {
item: file_name.to_string_lossy().to_string(),
span: self.name.span,
};
let mut cmd_process = new_command
.create_process(&input, true, head)?;
let mut cmd_process = ForegroundProcess::new(
new_command
.create_process(&input, true, head)?,
engine_state.pipeline_externals_state.clone(),
);
child = cmd_process.spawn();
}
}
@ -221,7 +239,7 @@ impl ExternalCommand {
#[cfg(not(windows))]
{
child = process.spawn()
child = fg_process.spawn()
}
match child {
@ -246,8 +264,23 @@ impl ExternalCommand {
let suggestion = suggest_command(&self.name.item, engine_state);
let label = match suggestion {
Some(s) => format!("did you mean '{s}'?"),
None => "can't run executable".into(),
Some(s) => {
if reconfirm_command_name {
format!(
"'{}' was not found, did you mean '{s}'?",
self.name.item
)
} else {
format!("did you mean '{s}'?")
}
}
None => {
if reconfirm_command_name {
format!("executable '{}' was not found", self.name.item)
} else {
"executable was not found".into()
}
}
};
Err(ShellError::ExternalCommand(
@ -273,7 +306,7 @@ impl ExternalCommand {
engine_state.config.use_ansi_coloring = false;
// if there is a string or a stream, that is sent to the pipe std
if let Some(mut stdin_write) = child.stdin.take() {
if let Some(mut stdin_write) = child.as_mut().stdin.take() {
std::thread::spawn(move || {
let input = crate::Table::run(
&crate::Table,
@ -305,55 +338,20 @@ impl ExternalCommand {
let redirect_stderr = self.redirect_stderr;
let span = self.name.span;
let output_ctrlc = ctrlc.clone();
let stderr_ctrlc = ctrlc.clone();
let (stdout_tx, stdout_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
let (exit_code_tx, exit_code_rx) = mpsc::channel();
let stdout = child.as_mut().stdout.take();
let stderr = child.as_mut().stderr.take();
// If this external is not the last expression, then its output is piped to a channel
// and we create a ListStream that can be consumed
//
// Create two threads: one for redirect stdout message, and wait for child process to complete.
// The other may be created when we want to redirect stderr message.
std::thread::spawn(move || {
// If this external is not the last expression, then its output is piped to a channel
// and we create a ListStream that can be consumed
if redirect_stderr {
let stderr = child.stderr.take().ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stderr from external".to_string(),
"Redirects need access to stderr of an external command"
.to_string(),
span,
)
})?;
// Stderr is read using the Buffer reader. It will do so until there is an
// error or there are no more bytes to read
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stderr);
while let Ok(bytes) = buf_read.fill_buf() {
if bytes.is_empty() {
break;
}
// The Cow generated from the function represents the conversion
// from bytes to String. If no replacements are required, then the
// borrowed value is a proper UTF-8 string. The Owned option represents
// a string where the values had to be replaced, thus marking it as bytes
let bytes = bytes.to_vec();
let length = bytes.len();
buf_read.consume(length);
if let Some(ctrlc) = &ctrlc {
if ctrlc.load(Ordering::SeqCst) {
break;
}
}
match stderr_tx.send(bytes) {
Ok(_) => continue,
Err(_) => break,
}
}
}
if redirect_stdout {
let stdout = child.stdout.take().ok_or_else(|| {
let stdout = stdout.ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stdout from external".to_string(),
"Redirects need access to stdout of an external command"
@ -362,36 +360,10 @@ impl ExternalCommand {
)
})?;
// Stdout is read using the Buffer reader. It will do so until there is an
// error or there are no more bytes to read
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stdout);
while let Ok(bytes) = buf_read.fill_buf() {
if bytes.is_empty() {
break;
}
// The Cow generated from the function represents the conversion
// from bytes to String. If no replacements are required, then the
// borrowed value is a proper UTF-8 string. The Owned option represents
// a string where the values had to be replaced, thus marking it as bytes
let bytes = bytes.to_vec();
let length = bytes.len();
buf_read.consume(length);
if let Some(ctrlc) = &ctrlc {
if ctrlc.load(Ordering::SeqCst) {
break;
}
}
match stdout_tx.send(bytes) {
Ok(_) => continue,
Err(_) => break,
}
}
read_and_redirect_message(stdout, stdout_tx, ctrlc)
}
match child.wait() {
match child.as_mut().wait() {
Err(err) => Err(ShellError::ExternalCommand(
"External command exited with error".into(),
err.to_string(),
@ -415,6 +387,24 @@ impl ExternalCommand {
}
}
});
let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
if redirect_stderr {
std::thread::spawn(move || {
let stderr = stderr.ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stderr from external".to_string(),
"Redirects need access to stderr of an external command"
.to_string(),
span,
)
})?;
read_and_redirect_message(stderr, stderr_tx, stderr_ctrlc);
Ok::<(), ShellError>(())
});
}
let stdout_receiver = ChannelReceiver::new(stdout_rx);
let stderr_receiver = ChannelReceiver::new(stderr_rx);
let exit_code_receiver = ValueReceiver::new(exit_code_rx);
@ -429,11 +419,15 @@ impl ExternalCommand {
} else {
None
},
stderr: Some(RawStream::new(
Box::new(stderr_receiver),
output_ctrlc.clone(),
head,
)),
stderr: if redirect_stderr {
Some(RawStream::new(
Box::new(stderr_receiver),
output_ctrlc.clone(),
head,
))
} else {
None
},
exit_code: Some(ListStream::from_stream(
Box::new(exit_code_receiver),
output_ctrlc,
@ -689,6 +683,46 @@ fn remove_quotes(input: String) -> String {
}
}
// read message from given `reader`, and send out through `sender`.
//
// `ctrlc` is used to control the process, if ctrl-c is pressed, the read and redirect
// process will be breaked.
fn read_and_redirect_message<R>(
reader: R,
sender: SyncSender<Vec<u8>>,
ctrlc: Option<Arc<AtomicBool>>,
) where
R: Read,
{
// read using the BufferReader. It will do so until there is an
// error or there are no more bytes to read
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader);
while let Ok(bytes) = buf_read.fill_buf() {
if bytes.is_empty() {
break;
}
// The Cow generated from the function represents the conversion
// from bytes to String. If no replacements are required, then the
// borrowed value is a proper UTF-8 string. The Owned option represents
// a string where the values had to be replaced, thus marking it as bytes
let bytes = bytes.to_vec();
let length = bytes.len();
buf_read.consume(length);
if let Some(ctrlc) = &ctrlc {
if ctrlc.load(Ordering::SeqCst) {
break;
}
}
match sender.send(bytes) {
Ok(_) => continue,
Err(_) => break,
}
}
}
// Receiver used for the RawStream
// It implements iterator so it can be used as a RawStream
struct ChannelReceiver {

File diff suppressed because it is too large Load Diff

View File

@ -40,3 +40,36 @@ fn alias_hiding_2() {
assert_eq!(actual.out, "0");
}
#[test]
fn alias_fails_with_invalid_name() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
alias 1234 = echo "test"
"#
));
assert!(actual
.err
.contains("alias name can't be a number or a filesize"));
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
alias 5gib = echo "test"
"#
));
assert!(actual
.err
.contains("alias name can't be a number or a filesize"));
}
#[test]
fn alias_alone_lists_aliases() {
let actual = nu!(
cwd: ".", pipeline(
r#"
alias a = 3; alias
"#
));
assert!(actual.out.contains("alias") && actual.out.contains("expansion"));
}

View File

@ -3,6 +3,20 @@ use nu_test_support::nu;
use nu_test_support::playground::Playground;
use std::path::PathBuf;
#[test]
fn cd_works_with_in_var() {
Playground::setup("cd_test_1", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
"cd_test_1" | cd $in; $env.PWD | path split | last
"#
);
assert_eq!("cd_test_1", actual.out);
})
}
// FIXME: jt: needs more work
#[ignore]
#[test]

View File

@ -5,9 +5,147 @@ fn capture_errors_works() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {$env.use} | describe
do -c {$env.use}
"#
));
assert_eq!(actual.out, "error");
assert!(actual.err.contains("column_not_found"));
}
#[test]
fn capture_errors_works_for_external() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail}
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]
fn capture_errors_works_for_external_with_pipeline() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail} | echo `text`
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]
fn capture_errors_works_for_external_with_semicolon() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {nu --testbin fail}; echo `text`
"#
));
assert!(actual.err.contains("External command runs to failed"));
assert_eq!(actual.out, "");
}
#[test]
fn do_with_semicolon_break_on_failed_external() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do { nu --not_exist_flag }; `text`
"#
));
assert_eq!(actual.out, "");
}
#[test]
#[cfg(not(windows))]
fn ignore_error_with_too_much_stderr_not_hang_nushell() {
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::pipeline;
use nu_test_support::playground::Playground;
Playground::setup("external with many stderr message", |dirs, sandbox| {
let bytes: usize = 81920;
let mut large_file_body = String::with_capacity(bytes);
for _ in 0..bytes {
large_file_body.push('a');
}
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
do -i {sh -c "cat a_large_file.txt 1>&2"} | complete | get stderr
"#
));
assert_eq!(actual.out, large_file_body);
})
}
#[test]
#[cfg(not(windows))]
fn ignore_error_with_too_much_stdout_not_hang_nushell() {
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::pipeline;
use nu_test_support::playground::Playground;
Playground::setup("external with many stdout message", |dirs, sandbox| {
let bytes: usize = 81920;
let mut large_file_body = String::with_capacity(bytes);
for _ in 0..bytes {
large_file_body.push('a');
}
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
do -i {sh -c "cat a_large_file.txt"} | complete | get stdout
"#
));
assert_eq!(actual.out, large_file_body);
})
}
#[test]
#[cfg(not(windows))]
fn ignore_error_with_both_stdout_stderr_messages_not_hang_nushell() {
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
Playground::setup(
"external with many stdout and stderr messages",
|dirs, sandbox| {
let script_body = r#"
x=$(printf '=%.0s' {1..40960})
echo $x
echo $x 1>&2
"#;
let mut expect_body = String::new();
for _ in 0..40960 {
expect_body.push('=');
}
sandbox.with_files(vec![FileWithContent("test.sh", script_body)]);
// check for stdout
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
do -i {bash test.sh} | complete | get stdout | str trim
"#
));
assert_eq!(actual.out, expect_body);
// check for stderr
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
do -i {bash test.sh} | complete | get stderr | str trim
"#
));
assert_eq!(actual.out, expect_body);
},
)
}

View File

@ -65,3 +65,17 @@ fn gets_first_row_when_no_amount_given() {
assert_eq!(actual.out, "1");
})
}
#[test]
fn gets_first_row_as_list_when_amount_given() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[1, 2, 3]
| first 1
| describe
"#
));
assert_eq!(actual.out, "list<int>");
}

View File

@ -0,0 +1,39 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn empty_glob_pattern_triggers_error() {
Playground::setup("glob_test_1", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jonathan.txt"),
EmptyFile("andres.txt"),
]);
let actual = nu!(
cwd: dirs.test(),
"glob ''",
);
assert!(actual.err.contains("must not be empty"));
})
}
#[test]
fn nonempty_glob_lists_matching_paths() {
Playground::setup("glob_sanity_star", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jonathan.txt"),
EmptyFile("andres.txt"),
]);
let actual = nu!(
cwd: dirs.test(),
pipeline("glob '*' | length"),
);
assert_eq!(actual.out, "3");
})
}

View File

@ -64,3 +64,17 @@ fn requests_more_rows_than_table_has() {
assert_eq!(actual.out, "1");
}
#[test]
fn gets_last_row_as_list_when_amount_given() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[1, 2, 3]
| last 1
| describe
"#
));
assert_eq!(actual.out, "list<int>");
}

View File

@ -9,7 +9,7 @@ fn lines() {
| lines
| skip while $it != "[dependencies]"
| skip 1
| first 1
| first
| split column "="
| get column1.0
| str trim

View File

@ -282,6 +282,66 @@ fn modulo() {
assert_eq!(actual.out, "1");
}
#[test]
fn unit_multiplication_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
1mb * 2
"#
));
assert_eq!(actual.out, "1.9 MiB");
}
#[test]
fn unit_multiplication_float_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
1mb * 1.2
"#
));
assert_eq!(actual.out, "1.1 MiB");
}
#[test]
fn unit_float_floor_division_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
1mb // 3.0
"#
));
assert_eq!(actual.out, "325.5 KiB");
}
#[test]
fn unit_division_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
1mb / 4
"#
));
assert_eq!(actual.out, "244.1 KiB");
}
#[test]
fn unit_float_division_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
1mb / 3.1
"#
));
assert_eq!(actual.out, "315.0 KiB");
}
#[test]
fn duration_math() {
let actual = nu!(
@ -342,6 +402,18 @@ fn duration_decimal_math_with_all_units() {
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
}
#[test]
fn duration_decimal_dans_test() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
3.14sec
"#
));
assert_eq!(actual.out, "3sec 140ms");
}
#[test]
fn duration_math_with_negative() {
let actual = nu!(

View File

@ -24,6 +24,7 @@ mod flatten;
mod format;
mod g;
mod get;
mod glob;
mod group_by;
mod hash_;
mod headers;
@ -74,6 +75,7 @@ mod split_by;
mod split_column;
mod split_row;
mod str_;
mod table;
mod take;
mod touch;
mod transpose;

View File

@ -154,7 +154,7 @@ fn parses_tsv() {
cwd: "tests/fixtures/formats", pipeline(
r#"
open caco3_plastics.tsv
| first 1
| first
| get origin
"#
));
@ -216,7 +216,7 @@ fn parses_arrow_ipc() {
r#"
open-df caco3_plastics.arrow
| into nu
| first 1
| first
| get origin
"#
));

View File

@ -4,7 +4,7 @@ use nu_test_support::nu;
fn can_get_reverse_first() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"ls | sort-by name | reverse | first 1 | get name | str trim "
"ls | sort-by name | reverse | first | get name | str trim "
);
assert_eq!(actual.out, "utf16.ini");

View File

@ -186,6 +186,7 @@ fn external_arg_with_variable_name() {
})
}
#[cfg(not(windows))]
#[test]
fn external_command_escape_args() {
Playground::setup("external failed command with semicolon", |dirs, _| {

View File

@ -12,7 +12,7 @@ fn by_column() {
| split column "="
| sort-by column1
| skip 1
| first 1
| first
| get column1
| str trim
"#
@ -33,7 +33,7 @@ fn by_invalid_column() {
| split column "="
| sort-by ColumnThatDoesNotExist
| skip 1
| first 1
| first
| get column1
| str trim
"#
@ -69,7 +69,7 @@ fn sort_primitive_values() {
| skip 1
| first 6
| sort-by
| first 1
| first
"#
));

View File

@ -284,12 +284,12 @@ fn source_env_is_scoped() {
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
assert!(actual.err.contains("can't run executable"));
assert!(actual.err.contains("executable was not found"));
let inp = &[r#"source-env spam.nu"#, r#"nor-similar-to-this"#];
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
assert!(actual.err.contains("can't run executable"));
assert!(actual.err.contains("executable was not found"));
})
}

View File

@ -172,18 +172,6 @@ fn from_nothing() {
assert_eq!(actual.out, "");
}
#[test]
fn from_error() {
let actual = nu!(
cwd: ".", pipeline(
r#"
do -c {$env.use} | into string
"#
));
assert_eq!(actual.out, "nu::shell::column_not_found");
}
#[test]
fn int_into_string() {
let actual = nu!(

View File

@ -0,0 +1,162 @@
use nu_test_support::nu;
#[test]
fn table_0() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table"#);
assert_eq!(
actual.out,
"╭───┬───┬───┬────────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼────────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ [list 3 items] │\
╰───┴───┴───┴────────────────╯"
);
}
#[test]
fn table_collapse_0() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table --collapse"#);
assert_eq!(
actual.out,
"\u{1b}[37m╭───\u{1b}[39m\u{1b}[37m┬───\u{1b}[39m\u{1b}[37m┬───╮\u{1b}[39m\u{1b}[37m│\u{1b}[39m a \u{1b}[37m│\u{1b}[39m b \u{1b}[37m│\u{1b}[39m c \u{1b}[37m│\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m 1 \u{1b}[37m│\u{1b}[39m 2 \u{1b}[37m│\u{1b}[39m 3 \u{1b}[37m│\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ───\u{1b}[39m\u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m 4 \u{1b}[37m│\u{1b}[39m 5 \u{1b}[37m│\u{1b}[39m 1 \u{1b}[37m│\u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m 2 \u{1b}[37m│\u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m ─── \u{1b}[39m\u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m \u{1b}[37m│\u{1b}[39m 3 \u{1b}[37m│\u{1b}[39m\u{1b}[37m╰───\u{1b}[39m\u{1b}[37m┴───\u{1b}[39m\u{1b}[37m┴───╯\u{1b}[39m"
);
}
#[test]
fn table_expand_0() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table --expand"#);
assert_eq!(
actual.out,
"╭───┬───┬───┬───────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼───────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ ╭───┬───╮ │\
│ │ │ │ │ 0 │ 1 │ │\
│ │ │ │ │ 1 │ 2 │ │\
│ │ │ │ │ 2 │ 3 │ │\
│ │ │ │ ╰───┴───╯ │\
╰───┴───┴───┴───────────╯"
);
}
#[test]
fn table_expand_deep_0() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 2 3]]]] | table --expand --expand-deep=1"#);
assert_eq!(
actual.out,
"╭───┬───┬───┬────────────────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼────────────────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ ╭───┬────────────────╮ │\
│ │ │ │ │ 0 │ 1 │ │\
│ │ │ │ │ 1 │ 2 │ │\
│ │ │ │ │ 2 │ [list 3 items] │ │\
│ │ │ │ ╰───┴────────────────╯ │\
╰───┴───┴───┴────────────────────────╯"
);
}
#[test]
fn table_expand_deep_1() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 2 3]]]] | table --expand --expand-deep=0"#);
assert_eq!(
actual.out,
"╭───┬───┬───┬────────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼────────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ [list 3 items] │\
╰───┴───┴───┴────────────────╯"
);
}
#[test]
fn table_expand_flatten_0() {
let actual = nu!(r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 1 1]]]] | table --expand --flatten "#);
assert_eq!(
actual.out,
"╭───┬───┬───┬───────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼───────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ ╭───┬───────╮ │\
│ │ │ │ │ 0 │ 1 │ │\
│ │ │ │ │ 1 │ 2 │ │\
│ │ │ │ │ 2 │ 1 1 1 │ │\
│ │ │ │ ╰───┴───────╯ │\
╰───┴───┴───┴───────────────╯"
);
}
#[test]
fn table_expand_flatten_1() {
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 1 1]]]] | table --expand --flatten --flatten-separator=,"#
);
assert_eq!(
actual.out,
"╭───┬───┬───┬───────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼───────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ ╭───┬───────╮ │\
│ │ │ │ │ 0 │ 1 │ │\
│ │ │ │ │ 1 │ 2 │ │\
│ │ │ │ │ 2 │ 1,1,1 │ │\
│ │ │ │ ╰───┴───────╯ │\
╰───┴───┴───┴───────────────╯"
);
}
#[test]
fn table_expand_flatten_and_deep_1() {
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 [1 [1 1 1] 1]]]] | table --expand --expand-deep=2 --flatten --flatten-separator=,"#
);
assert_eq!(
actual.out,
"╭───┬───┬───┬────────────────────────────────╮\
│ # │ a │ b │ c │\
├───┼───┼───┼────────────────────────────────┤\
│ 0 │ 1 │ 2 │ 3 │\
│ 1 │ 4 │ 5 │ ╭───┬────────────────────────╮ │\
│ │ │ │ │ 0 │ 1 │ │\
│ │ │ │ │ 1 │ 2 │ │\
│ │ │ │ │ 2 │ ╭───┬────────────────╮ │ │\
│ │ │ │ │ │ │ 0 │ 1 │ │ │\
│ │ │ │ │ │ │ 1 │ [list 3 items] │ │ │\
│ │ │ │ │ │ │ 2 │ 1 │ │ │\
│ │ │ │ │ │ ╰───┴────────────────╯ │ │\
│ │ │ │ ╰───┴────────────────────────╯ │\
╰───┴───┴───┴────────────────────────────────╯"
);
}
#[test]
#[cfg(not(windows))]
fn external_with_too_much_stdout_should_not_hang_nu() {
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::pipeline;
use nu_test_support::playground::Playground;
Playground::setup("external with too much stdout", |dirs, sandbox| {
let bytes: usize = 81920;
let mut large_file_body = String::with_capacity(bytes);
for _ in 0..bytes {
large_file_body.push('a');
}
sandbox.with_files(vec![FileWithContent("a_large_file.txt", &large_file_body)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
cat a_large_file.txt | table
"#
));
assert_eq!(actual.out, large_file_body);
})
}

View File

@ -1,4 +1,4 @@
use chrono::{Date, DateTime, Local, TimeZone};
use chrono::{Date, DateTime, Local};
use nu_test_support::fs::Stub;
use nu_test_support::nu;
use nu_test_support::playground::Playground;
@ -32,185 +32,6 @@ fn creates_two_files() {
})
}
#[test]
fn change_modified_time_of_file() {
Playground::setup("change_time_test_3", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn create_and_change_modified_time_of_file() {
Playground::setup("change_time_test_4", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -m -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_file_no_year() {
Playground::setup("change_time_test_5", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 08241230.12 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_6", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 08241230 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_files() {
Playground::setup("change_time_test_7", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.30 file.txt file2.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
let path = dirs.test().join("file2.txt");
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn errors_if_change_modified_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_8", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 082412.3012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 0824.123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 08.24123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 0.824123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_modified_time_of_file_to_today() {
Playground::setup("change_time_test_9", |dirs, sandbox| {
@ -232,234 +53,6 @@ fn change_modified_time_of_file_to_today() {
})
}
#[test]
fn change_modified_time_to_date() {
Playground::setup("change_time_test_10", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -m -d "August 24, 2019; 12:30:30" file.txt"#
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_to_time_of_reference() {
Playground::setup("change_time_test_11", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
nu!(
cwd: dirs.test(),
r#"touch -m -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -m -r reference.txt file.txt"#
);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_time: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(time, ref_time);
})
}
#[test]
fn change_access_time_of_file() {
Playground::setup("change_time_test_12", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn create_and_change_access_time_of_file() {
Playground::setup("change_time_test_13", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -a -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_file_no_year() {
Playground::setup("change_time_test_14", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 08241230.12 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_15", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 08241230 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_files() {
Playground::setup("change_time_test_16", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.30 file.txt file2.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
let path = dirs.test().join("file2.txt");
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn errors_if_change_access_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_17", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 082412.3012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 0824.123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 08.24123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 0.824123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_access_time_of_file_to_today() {
Playground::setup("change_time_test_18", |dirs, sandbox| {
@ -481,246 +74,6 @@ fn change_access_time_of_file_to_today() {
})
}
#[test]
fn change_access_time_to_date() {
Playground::setup("change_time_test_19", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -a -d "August 24, 2019; 12:30:30" file.txt"#
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_to_time_of_reference() {
Playground::setup("change_time_test_20", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
nu!(
cwd: dirs.test(),
r#"touch -a -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -a -r reference.txt file.txt"#
);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_time: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, ref_time);
})
}
#[test]
fn change_modified_and_access_time_of_file() {
Playground::setup("change_time_test_21", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -a -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let metadata = path.metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn create_and_change_modified_and_access_time_of_file() {
Playground::setup("change_time_test_22", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let metadata = path.metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_file_no_year() {
Playground::setup("change_time_test_23", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -m -t 08241230.12 file.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_24", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -t 08241230 file.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_files() {
Playground::setup("change_time_test_25", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -a -m -t 1908241230.30 file.txt file2.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
let metadata = dirs.test().join("file2.txt").metadata().unwrap();
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn errors_if_change_modified_and_access_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_26", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -m -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 082412.3012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 0824.123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 08.24123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 0.824123012 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_modified_and_access_time_of_file_to_today() {
Playground::setup("change_time_test_27", |dirs, sandbox| {
@ -743,97 +96,12 @@ fn change_modified_and_access_time_of_file_to_today() {
})
}
#[test]
fn change_modified_and_access_time_to_date() {
Playground::setup("change_time_test_28", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -d "August 24, 2019; 12:30:30" file.txt"#
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_to_time_of_reference() {
Playground::setup("change_time_test_29", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
// Set the same time for the modified and access time of the reference file
nu!(
cwd: dirs.test(),
r#"touch -a -m -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -r reference.txt file.txt"#
);
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_atime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(atime, ref_atime);
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_mtime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(mtime, ref_mtime);
// Set different time for the modified and access time of the reference file
nu!(
cwd: dirs.test(),
r#"touch -a -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -m -t 202009251340.40 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -a -m -r reference.txt file.txt"#
);
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_atime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(atime, ref_atime);
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_mtime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(mtime, ref_mtime);
})
}
#[test]
fn not_create_file_if_it_not_exists() {
Playground::setup("change_time_test_28", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
r#"touch -c -d "August 24, 2019; 12:30:30" file.txt"#
r#"touch -c file.txt"#
);
let path = dirs.test().join("file.txt");
@ -850,20 +118,3 @@ fn not_create_file_if_it_not_exists() {
assert!(!path.exists());
})
}
#[test]
fn test_invalid_timestamp() {
Playground::setup("test_invalid_timestamp", |dirs, _sandbox| {
let outcome = nu!(
cwd: dirs.test(),
r#"touch -t 20220729. file.txt"#
);
assert!(outcome.err.contains("input has an invalid timestamp"));
let outcome = nu!(
cwd: dirs.test(),
r#"touch -t 20220729120099 file.txt"#
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}

View File

@ -6,7 +6,7 @@ use nu_test_support::pipeline;
fn filters_by_unit_size_comparison() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"ls | where size > 1kib | sort-by size | get name | first 1 | str trim"
"ls | where size > 1kib | sort-by size | get name | first | str trim"
);
assert_eq!(actual.out, "cargo_sample.toml");
@ -103,7 +103,7 @@ fn binary_operator_comparisons() {
open sample.db
| get ints
| where z != 1
| first 1
| first
| get z
"#
));

View File

@ -6,7 +6,7 @@ use nu_test_support::{nu, pipeline};
fn table_to_csv_text_and_from_csv_text_back_into_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"open caco3_plastics.csv | to csv | from csv | first 1 | get origin "
"open caco3_plastics.csv | to csv | from csv | first | get origin "
);
assert_eq!(actual.out, "SPAIN");

View File

@ -103,6 +103,51 @@ fn to_nuon_escaping2() {
assert_eq!(actual.out, "hello\\world");
}
#[test]
fn to_nuon_escaping3() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
["hello\\world"]
| to nuon
| from nuon
| $in == [hello\world]
"#
));
assert_eq!(actual.out, "true");
}
#[test]
fn to_nuon_escaping4() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
["hello\"world"]
| to nuon
| from nuon
| $in == ["hello\"world"]
"#
));
assert_eq!(actual.out, "true");
}
#[test]
fn to_nuon_escaping5() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
{s: "hello\"world"}
| to nuon
| from nuon
| $in == {s: "hello\"world"}
"#
));
assert_eq!(actual.out, "true");
}
#[test]
fn to_nuon_negative_int() {
let actual = nu!(

View File

@ -6,7 +6,7 @@ use nu_test_support::{nu, pipeline};
fn table_to_tsv_text_and_from_tsv_text_back_into_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"open caco3_plastics.tsv | to tsv | from tsv | first 1 | get origin"
"open caco3_plastics.tsv | to tsv | from tsv | first | get origin"
);
assert_eq!(actual.out, "SPAIN");
@ -16,7 +16,7 @@ fn table_to_tsv_text_and_from_tsv_text_back_into_table() {
fn table_to_tsv_text_and_from_tsv_text_back_into_table_using_csv_separator() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r"open caco3_plastics.tsv | to tsv | from csv --separator '\t' | first 1 | get origin"
r"open caco3_plastics.tsv | to tsv | from csv --separator '\t' | first | get origin"
);
assert_eq!(actual.out, "SPAIN");

View File

@ -5,13 +5,13 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
edition = "2021"
license = "MIT"
name = "nu-engine"
version = "0.69.0"
version = "0.70.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-glob = { path = "../nu-glob", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-glob = { path = "../nu-glob", version = "0.70.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
chrono = { version="0.4.21", features=["serde"] }
sysinfo = "0.26.2"

View File

@ -4,7 +4,7 @@ use nu_protocol::{
ast::{Block, Call, Expr, Expression, Operator},
engine::{EngineState, Stack, Visibility},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, Range, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
PipelineData, Range, RawStream, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
ENV_VARIABLE_ID,
};
use nu_utils::stdout_write_all_and_flush;
@ -201,7 +201,7 @@ fn eval_external(
input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
) -> Result<PipelineData, ShellError> {
let decl_id = engine_state
.find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head.span))?;
@ -238,54 +238,7 @@ fn eval_external(
))
}
// when the external command doesn't redirect output, we eagerly check the result
// and find if the command runs to failed.
let mut runs_to_failed = false;
let result = command.run(engine_state, stack, &call, input)?;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = result
{
let exit_code = exit_code.take();
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
))
}
None => Ok((
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
)),
}
} else {
Ok((result, runs_to_failed))
}
command.run(engine_state, stack, &call, input)
}
pub fn eval_expression(
@ -383,7 +336,6 @@ pub fn eval_expression(
false,
false,
)?
.0
.into_value(span))
}
Expr::DateTime(dt) => Ok(Value::Date {
@ -408,7 +360,7 @@ pub fn eval_expression(
match op {
Operator::And => {
if !lhs.is_true() {
if lhs.is_false() {
Ok(Value::Bool {
val: false,
span: expr.span,
@ -682,7 +634,6 @@ pub fn eval_expression_with_input(
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
let mut external_failed = false;
match expr {
Expression {
expr: Expr::Call(call),
@ -702,7 +653,7 @@ pub fn eval_expression_with_input(
expr: Expr::ExternalCall(head, args),
..
} => {
let external_result = eval_external(
input = eval_external(
engine_state,
stack,
head,
@ -711,8 +662,6 @@ pub fn eval_expression_with_input(
redirect_stdout,
redirect_stderr,
)?;
input = external_result.0;
external_failed = external_result.1
}
Expression {
@ -728,9 +677,88 @@ pub fn eval_expression_with_input(
elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
}
}
};
Ok((input, external_failed))
Ok(might_consume_external_result(input))
}
// if the result is ExternalStream without redirecting output.
// that indicates we have no more commands to execute currently.
// we can try to catch and detect if external command runs to failed.
//
// This is useful to commands with semicolon, we can detect errors early to avoid
// commands after semicolon running.
fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
let mut runs_to_failed = false;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = input
{
let exit_code = exit_code.take();
// Note:
// In run-external's implementation detail, the result sender thread
// send out stderr message first, then stdout message, then exit_code.
//
// In this clause, we already make sure that `stdout` is None
// But not the case of `stderr`, so if `stderr` is not None
// We need to consume stderr message before reading external commands' exit code.
//
// Or we'll never have a chance to read exit_code if stderr producer produce too much stderr message.
// So we consume stderr stream and rebuild it.
let stderr = stderr.map(|stderr_stream| {
let stderr_ctrlc = stderr_stream.ctrlc.clone();
let stderr_span = stderr_stream.span;
let stderr_bytes = match stderr_stream.into_bytes() {
Err(_) => vec![],
Ok(bytes) => bytes.item,
};
RawStream::new(
Box::new(vec![Ok(stderr_bytes)].into_iter()),
stderr_ctrlc,
stderr_span,
)
});
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
(
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
)
}
None => (
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
),
}
} else {
(input, false)
}
}
pub fn eval_block(
@ -1538,20 +1566,36 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
val: size * 1000 * 1000 * 1000,
span,
},
Unit::Minute => Value::Duration {
val: size * 1000 * 1000 * 1000 * 60,
span,
Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Hour => Value::Duration {
val: size * 1000 * 1000 * 1000 * 60 * 60,
span,
Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"day duration too large".into(),
"day duration too large".into(),
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
@ -1562,8 +1606,8 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"week duration too large".into(),
"week duration too large".into(),
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),

View File

@ -1,6 +1,6 @@
[package]
name = "nu-glob"
version = "0.69.0"
version = "0.70.0"
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
license = "MIT/Apache-2.0"
description = """

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
edition = "2021"
license = "MIT"
name = "nu-json"
version = "0.69.0"
version = "0.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -21,5 +21,5 @@ num-traits = "0.2.14"
serde = "1.0"
[dev-dependencies]
nu-path = { path="../nu-path", version = "0.69.0" }
nu-path = { path="../nu-path", version = "0.70.0" }
serde_json = "1.0"

View File

@ -5,18 +5,19 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
edition = "2021"
license = "MIT"
name = "nu-parser"
version = "0.69.0"
version = "0.70.0"
[dependencies]
bytesize = "1.1.0"
chrono = "0.4.21"
itertools = "0.10"
miette = "5.1.0"
thiserror = "1.0.31"
serde_json = "1.0"
nu-path = {path = "../nu-path", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.69.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-path = {path = "../nu-path", version = "0.70.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
log = "0.4"
[features]

View File

@ -112,6 +112,10 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::variable_not_valid), url(docsrs))]
VariableNotValid(#[label = "variable name can't contain spaces or quotes"] Span),
#[error("Alias name not supported.")]
#[diagnostic(code(nu::parser::variable_not_valid), url(docsrs))]
AliasNotValid(#[label = "alias name can't be a number or a filesize"] Span),
#[error("Module not found.")]
#[diagnostic(
code(nu::parser::module_not_found),
@ -344,6 +348,7 @@ impl ParseError {
ParseError::MultipleRestParams(s) => *s,
ParseError::VariableNotFound(s) => *s,
ParseError::VariableNotValid(s) => *s,
ParseError::AliasNotValid(s) => *s,
ParseError::ModuleNotFound(s) => *s,
ParseError::CyclicalModuleImport(_, s) => *s,
ParseError::ModuleOrOverlayNotFound(s) => *s,

View File

@ -2,7 +2,7 @@ use nu_path::canonicalize_with;
use nu_protocol::{
ast::{
Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
ImportPatternMember, Pipeline,
ImportPatternMember, PathMember, Pipeline,
},
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
span, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type,
@ -544,16 +544,72 @@ pub fn parse_alias(
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let (name_span, split_id) =
// if the call is "alias", turn it into "print $nu.scope.aliases"
if spans.len() == 1 {
let head = Expression {
expr: Expr::Var(nu_protocol::NU_VARIABLE_ID),
span: Span::new(0, 0),
ty: Type::Any,
custom_completion: None,
};
let tail = vec![
PathMember::String {
val: "scope".to_string(),
span: Span::new(0, 0),
},
PathMember::String {
val: "aliases".to_string(),
span: Span::new(0, 0),
},
];
let expr = Expression {
ty: Type::Any,
expr: Expr::FullCellPath(Box::new(nu_protocol::ast::FullCellPath { head, tail })),
span: Span::new(0, 0),
custom_completion: None,
};
if let Some(decl_id) = working_set.find_decl(b"print", &Type::Any) {
let print_call = Expr::Call(Box::new(Call {
head: spans[0],
arguments: vec![Argument::Positional(expr)],
decl_id,
redirect_stdout: true,
redirect_stderr: false,
}));
return (
Pipeline::from_vec(vec![Expression {
expr: print_call,
span: spans[0],
ty: Type::Any,
custom_completion: None,
}]),
None,
);
}
return (Pipeline::from_vec(vec![expr]), None);
}
let (name_span, alias_name, split_id) =
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
(spans[1], 2)
(spans[1], spans.get(2), 2)
} else {
(spans[0], 1)
(spans[0], spans.get(1), 1)
};
let name = working_set.get_span_contents(name_span);
if name == b"alias" {
if let Some(alias_name) = alias_name {
let alias_name = String::from_utf8_lossy(working_set.get_span_contents(*alias_name));
if alias_name.parse::<bytesize::ByteSize>().is_ok() || alias_name.parse::<f64>().is_ok()
{
return (
Pipeline::from_vec(vec![garbage(name_span)]),
Some(ParseError::AliasNotValid(name_span)),
);
}
}
if let Some((span, err)) = check_name(working_set, spans) {
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
}

View File

@ -2175,23 +2175,50 @@ pub fn parse_duration(
}
}
pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
return Some((1_f64 / x) as i64);
// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
pub fn modf(x: f64) -> (f64, f64) {
let rv2: f64;
let mut u = x.to_bits();
let e = ((u >> 52 & 0x7ff) as i32) - 0x3ff;
/* no fractional part */
if e >= 52 {
rv2 = x;
if e == 0x400 && (u << 12) != 0 {
/* nan */
return (x, rv2);
}
None
u &= 1 << 63;
return (f64::from_bits(u), rv2);
}
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
/* no integral part*/
if e < 0 {
u &= 1 << 63;
rv2 = f64::from_bits(u);
return (x, rv2);
}
let mask = ((!0) >> 12) >> e;
if (u & mask) == 0 {
rv2 = x;
u &= 1 << 63;
return (f64::from_bits(u), rv2);
}
u &= !mask;
rv2 = f64::from_bits(u);
(x - rv2, rv2)
}
pub fn parse_duration_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
if num_with_unit_bytes.is_empty()
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
{
return None;
}
let token = String::from_utf8_lossy(bytes).to_string();
let upper = token.to_uppercase();
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
let uppercase_num_with_unit = num_with_unit.to_uppercase();
let unit_groups = [
(Unit::Nanosecond, "NS", None),
(Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
@ -2202,34 +2229,33 @@ pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
(Unit::Week, "WK", Some((Unit::Day, 7))),
];
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
let mut lhs = token;
if let Some(unit) = unit_groups
.iter()
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
{
let mut lhs = num_with_unit;
for _ in 0..unit.1.len() {
lhs.pop();
}
let input: Vec<&str> = lhs.split('.').collect();
let (value, unit_to_use) = match &input[..] {
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
[number_str, decimal_part_str] => match unit.2 {
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(),
parse_decimal_str_to_number(decimal_part_str),
) {
(Ok(number), Some(decimal_part)) => (
Some(
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
),
unit_to_convert_to.0,
),
_ => (None, unit.0),
},
None => (None, unit.0),
},
_ => (None, unit.0),
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
Ok(x) => x,
Err(_) => return None,
});
let (num, unit_to_use) = match unit.2 {
Some(unit_to_convert_to) => (
Some(
((number_part * unit_to_convert_to.1 as f64)
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
),
unit_to_convert_to.0,
),
None => (Some(number_part as i64), unit.0),
};
if let Some(x) = value {
if let Some(x) = num {
trace!("-- found {} {:?}", x, unit_to_use);
let lhs_span = Span::new(span.start, span.start + lhs.len());
@ -2262,33 +2288,32 @@ pub fn parse_filesize(
working_set: &StateWorkingSet,
span: Span,
) -> (Expression, Option<ParseError>) {
trace!("parsing: duration");
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
return Some((1_f64 / x) as i64);
}
None
}
trace!("parsing: filesize");
let bytes = working_set.get_span_contents(span);
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
return (
match parse_filesize_bytes(bytes, span) {
Some(expression) => (expression, None),
None => (
garbage(span),
Some(ParseError::Mismatch(
"filesize".into(),
"non-filesize unit".into(),
span,
)),
);
),
}
}
pub fn parse_filesize_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
if num_with_unit_bytes.is_empty()
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
{
return None;
}
let token = String::from_utf8_lossy(bytes).to_string();
let upper = token.to_uppercase();
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
let uppercase_num_with_unit = num_with_unit.to_uppercase();
let unit_groups = [
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
@ -2306,69 +2331,58 @@ pub fn parse_filesize(
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
(Unit::Byte, "B", None),
];
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
let mut lhs = token;
if let Some(unit) = unit_groups
.iter()
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
{
let mut lhs = num_with_unit;
for _ in 0..unit.1.len() {
lhs.pop();
}
let input: Vec<&str> = lhs.split('.').collect();
let (value, unit_to_use) = match &input[..] {
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
[number_str, decimal_part_str] => match unit.2 {
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(),
parse_decimal_str_to_number(decimal_part_str),
) {
(Ok(number), Some(decimal_part)) => (
Some(
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
),
unit_to_convert_to.0,
),
_ => (None, unit.0),
},
None => (None, unit.0),
},
_ => (None, unit.0),
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
Ok(x) => x,
Err(_) => return None,
});
let (num, unit_to_use) = match unit.2 {
Some(unit_to_convert_to) => (
Some(
((number_part * unit_to_convert_to.1 as f64)
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
),
unit_to_convert_to.0,
),
None => (Some(number_part as i64), unit.0),
};
if let Some(x) = value {
if let Some(x) = num {
trace!("-- found {} {:?}", x, unit_to_use);
let lhs_span = Span::new(span.start, span.start + lhs.len());
let unit_span = Span::new(span.start + lhs.len(), span.end);
return (
Expression {
expr: Expr::ValueWithUnit(
Box::new(Expression {
expr: Expr::Int(x),
span: lhs_span,
ty: Type::Number,
custom_completion: None,
}),
Spanned {
item: unit_to_use,
span: unit_span,
},
),
span,
ty: Type::Filesize,
custom_completion: None,
},
None,
);
return Some(Expression {
expr: Expr::ValueWithUnit(
Box::new(Expression {
expr: Expr::Int(x),
span: lhs_span,
ty: Type::Number,
custom_completion: None,
}),
Spanned {
item: unit_to_use,
span: unit_span,
},
),
span,
ty: Type::Filesize,
custom_completion: None,
});
}
}
(
garbage(span),
Some(ParseError::Mismatch(
"filesize".into(),
"non-filesize unit".into(),
span,
)),
)
None
}
pub fn parse_glob_pattern(
@ -5353,6 +5367,15 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
custom_completion: None,
}));
output.push(Argument::Named((
Spanned {
item: "keep-env".to_string(),
span: Span::new(0, 0),
},
None,
None,
)));
// The containing, synthetic call to `collect`.
// We don't want to have a real span as it will confuse flattening
// The args are where we'll get the real info

View File

@ -101,11 +101,14 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Int, Type::Filesize) => (Type::Filesize, None),
(Type::Duration, Type::Int) => (Type::Filesize, None),
(Type::Int, Type::Duration) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
(Type::Float, Type::Filesize) => (Type::Filesize, None),
(Type::Duration, Type::Int) => (Type::Duration, None),
(Type::Int, Type::Duration) => (Type::Duration, None),
(Type::Duration, Type::Float) => (Type::Duration, None),
(Type::Float, Type::Duration) => (Type::Duration, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
@ -157,10 +160,11 @@ pub fn math_result_type(
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Filesize, Type::Filesize) => (Type::Float, None),
(Type::Duration, Type::Duration) => (Type::Float, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
(Type::Duration, Type::Duration) => (Type::Float, None),
(Type::Duration, Type::Int) => (Type::Duration, None),
(Type::Duration, Type::Float) => (Type::Duration, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
@ -187,10 +191,11 @@ pub fn math_result_type(
(Type::Int, Type::Float) => (Type::Int, None),
(Type::Float, Type::Float) => (Type::Int, None),
(Type::Filesize, Type::Filesize) => (Type::Int, None),
(Type::Duration, Type::Duration) => (Type::Int, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
(Type::Duration, Type::Duration) => (Type::Int, None),
(Type::Duration, Type::Int) => (Type::Duration, None),
(Type::Duration, Type::Float) => (Type::Duration, None),
(Type::Any, _) => (Type::Any, None),
(_, Type::Any) => (Type::Any, None),

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
edition = "2021"
license = "MIT"
name = "nu-path"
version = "0.69.0"
version = "0.70.0"
[dependencies]
dirs-next = "2.0.0"

View File

@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
edition = "2021"
license = "MIT"
name = "nu-plugin"
version = "0.69.0"
version = "0.70.0"
[dependencies]
bincode = "1.3.3"
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-engine = { path = "../nu-engine", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
nu-engine = { path = "../nu-engine", version = "0.70.0" }
serde = {version = "1.0.143", features = ["derive"]}
serde_json = { version = "1.0"}
byte-order = "0.3.0"

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
edition = "2021"
license = "MIT"
name = "nu-pretty-hex"
version = "0.69.0"
version = "0.70.0"
[lib]
doctest = false

View File

@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
edition = "2021"
license = "MIT"
name = "nu-protocol"
version = "0.69.0"
version = "0.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-utils = { path = "../nu-utils", version = "0.69.0" }
nu-path = { path = "../nu-path", version = "0.69.0" }
nu-json = { path = "../nu-json", version = "0.69.0" }
nu-utils = { path = "../nu-utils", version = "0.70.0" }
nu-path = { path = "../nu-path", version = "0.70.0" }
nu-json = { path = "../nu-json", version = "0.70.0" }
byte-unit = "4.0.9"
chrono = { version="0.4.21", features=["serde"] }
@ -20,7 +20,7 @@ chrono-humanize = "0.2.1"
fancy-regex = "0.10.0"
indexmap = { version="1.7", features=["serde-1"] }
miette = { version = "5.1.0", features = ["fancy"] }
num-format = "0.4.0"
num-format = "0.4.3"
serde = {version = "1.0.130", features = ["derive"]}
serde_json = { version = "1.0", optional = true }
sys-locale = "0.2.0"

View File

@ -77,7 +77,7 @@ pub struct Config {
pub rm_always_trash: bool,
pub shell_integration: bool,
pub buffer_editor: String,
pub disable_table_indexes: bool,
pub table_index_mode: TableIndexMode,
pub cd_with_abbreviations: bool,
pub case_sensitive_completions: bool,
pub enable_external_completion: bool,
@ -114,7 +114,7 @@ impl Default for Config {
rm_always_trash: false,
shell_integration: false,
buffer_editor: String::new(),
disable_table_indexes: false,
table_index_mode: TableIndexMode::Always,
cd_with_abbreviations: false,
case_sensitive_completions: false,
enable_external_completion: true,
@ -145,6 +145,16 @@ pub enum HistoryFileFormat {
PlainText,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TableIndexMode {
/// Always show indexes
Always,
/// Never show indexes
Never,
/// Show indexes when a table has "index" column
Auto,
}
/// A Table view configuration, for a situation where
/// we need to limit cell width in order to adjust for a terminal size.
#[derive(Serialize, Deserialize, Clone, Debug)]
@ -370,11 +380,19 @@ impl Value {
eprintln!("$config.buffer_editor is not a string")
}
}
"disable_table_indexes" => {
if let Ok(b) = value.as_bool() {
config.disable_table_indexes = b;
"table_index_mode" => {
if let Ok(b) = value.as_string() {
let val_str = b.to_lowercase();
match val_str.as_ref() {
"always" => config.table_index_mode = TableIndexMode::Always,
"never" => config.table_index_mode = TableIndexMode::Never,
"auto" => config.table_index_mode = TableIndexMode::Auto,
_ => eprintln!(
"$config.table_index_mode must be a never, always or auto"
),
}
} else {
eprintln!("$config.disable_table_indexes is not a bool")
eprintln!("$config.table_index_mode is not a string")
}
}
"cd_with_abbreviations" => {

View File

@ -10,7 +10,10 @@ use std::path::Path;
use std::path::PathBuf;
use std::{
collections::{HashMap, HashSet, VecDeque},
sync::{atomic::AtomicBool, Arc, Mutex},
sync::{
atomic::{AtomicBool, AtomicU32},
Arc, Mutex,
},
};
static PWD_ENV: &str = "PWD";
@ -80,6 +83,7 @@ pub struct EngineState {
pub env_vars: EnvVars,
pub previous_env_vars: HashMap<String, Value>,
pub config: Config,
pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
pub repl_buffer_state: Arc<Mutex<Option<String>>>,
pub repl_operation_queue: Arc<Mutex<VecDeque<ReplOperation>>>,
#[cfg(feature = "plugin")]
@ -121,6 +125,7 @@ impl EngineState {
env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]),
previous_env_vars: HashMap::new(),
config: Config::default(),
pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
repl_buffer_state: Arc::new(Mutex::new(None)),
repl_operation_queue: Arc::new(Mutex::new(VecDeque::new())),
#[cfg(feature = "plugin")]

View File

@ -424,7 +424,7 @@ impl PipelineData {
stack: &mut Stack,
no_newline: bool,
to_stderr: bool,
) -> Result<(), ShellError> {
) -> Result<i64, ShellError> {
// If the table function is in the declarations, then we can use it
// to create the table value that will be printed in the terminal
@ -433,10 +433,14 @@ impl PipelineData {
if let PipelineData::ExternalStream {
stdout: stream,
stderr: stderr_stream,
exit_code,
..
} = self
{
// NOTE: currently we don't need anything from stderr
// so directly consumes `stderr_stream` to make sure that everything is done.
std::thread::spawn(move || stderr_stream.map(|x| x.into_bytes()));
if let Some(stream) = stream {
for s in stream {
let s_live = s?;
@ -452,10 +456,13 @@ impl PipelineData {
// Make sure everything has finished
if let Some(exit_code) = exit_code {
let _: Vec<_> = exit_code.into_iter().collect();
let mut exit_codes: Vec<_> = exit_code.into_iter().collect();
if let Some(Value::Int { val, .. }) = exit_codes.pop() {
return Ok(val);
}
}
return Ok(());
return Ok(0);
}
match engine_state.find_decl("table".as_bytes(), &[]) {
@ -474,7 +481,7 @@ impl PipelineData {
}
};
Ok(())
Ok(0)
}
fn write_all_and_flush(
@ -483,7 +490,7 @@ impl PipelineData {
config: &Config,
no_newline: bool,
to_stderr: bool,
) -> Result<(), ShellError> {
) -> Result<i64, ShellError> {
for item in self {
let mut out = if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
@ -506,7 +513,7 @@ impl PipelineData {
}
}
Ok(())
Ok(0)
}
}

View File

@ -1176,6 +1176,10 @@ impl Value {
matches!(self, Value::Bool { val: true, .. })
}
pub fn is_false(&self) -> bool {
matches!(self, Value::Bool { val: false, .. })
}
pub fn columns(&self) -> Vec<String> {
match self {
Value::Record { cols, .. } => cols.clone(),
@ -1676,6 +1680,7 @@ impl Value {
}),
}
}
pub fn sub(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
@ -1754,6 +1759,7 @@ impl Value {
}),
}
}
pub fn mul(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
@ -1790,6 +1796,18 @@ impl Value {
span,
})
}
(Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
Ok(Value::Filesize {
val: (*lhs * *rhs as f64) as i64,
span,
})
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::Filesize {
val: (*lhs as f64 * *rhs) as i64,
span,
})
}
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
Ok(Value::Duration {
val: *lhs * *rhs,
@ -1802,6 +1820,18 @@ impl Value {
span,
})
}
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::Duration {
val: (*lhs as f64 * *rhs) as i64,
span,
})
}
(Value::Float { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
Ok(Value::Duration {
val: (*lhs * *rhs as f64) as i64,
span,
})
}
(Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Multiply, op, rhs)
}
@ -1815,6 +1845,7 @@ impl Value {
}),
}
}
pub fn div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
@ -1881,6 +1912,26 @@ impl Value {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::Filesize {
val: ((*lhs as f64) / (*rhs as f64)) as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::Filesize {
val: (*lhs as f64 / rhs) as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if *rhs != 0 {
if lhs % rhs == 0 {
@ -1898,20 +1949,20 @@ impl Value {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::Filesize {
val: lhs / rhs,
Ok(Value::Duration {
val: ((*lhs as f64) / (*rhs as f64)) as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::Duration {
val: lhs / rhs,
val: ((*lhs as f64) / rhs) as i64,
span,
})
} else {
@ -1931,6 +1982,7 @@ impl Value {
}),
}
}
pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
@ -1998,6 +2050,32 @@ impl Value {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::Filesize {
val: ((*lhs as f64) / (*rhs as f64))
.max(std::i64::MIN as f64)
.min(std::i64::MAX as f64)
.floor() as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::Filesize {
val: (*lhs as f64 / *rhs)
.max(std::i64::MIN as f64)
.min(std::i64::MAX as f64)
.floor() as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::Int {
@ -2011,20 +2089,26 @@ impl Value {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::Filesize {
val: lhs / rhs,
Ok(Value::Duration {
val: (*lhs as f64 / *rhs as f64)
.max(std::i64::MIN as f64)
.min(std::i64::MAX as f64)
.floor() as i64,
span,
})
} else {
Err(ShellError::DivisionByZero(op))
}
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::Duration {
val: lhs / rhs,
val: (*lhs as f64 / *rhs)
.max(std::i64::MIN as f64)
.min(std::i64::MAX as f64)
.floor() as i64,
span,
})
} else {
@ -2044,6 +2128,7 @@ impl Value {
}),
}
}
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::LessThan, op, rhs);
@ -2070,6 +2155,7 @@ impl Value {
}),
}
}
pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::LessThanOrEqual, op, rhs);
@ -2096,6 +2182,7 @@ impl Value {
}),
}
}
pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::GreaterThan, op, rhs);
@ -2122,6 +2209,7 @@ impl Value {
}),
}
}
pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::GreaterThanOrEqual, op, rhs);
@ -2148,6 +2236,7 @@ impl Value {
}),
}
}
pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::Equal, op, rhs);
@ -2172,6 +2261,7 @@ impl Value {
},
}
}
pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::NotEqual, op, rhs);

View File

@ -148,7 +148,7 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.69.0"
version = "0.70.0"
dependencies = [
"errno",
"libproc",

View File

@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
description = "Nushell system querying"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
name = "nu-system"
version = "0.69.0"
version = "0.70.0"
edition = "2021"
license = "MIT"
@ -16,6 +16,10 @@ path = "src/main.rs"
[dependencies]
libc = "0.2"
[target.'cfg(target_family = "unix")'.dependencies]
nix = "0.24"
atty = "0.2"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
procfs = "0.14.0"

View File

@ -0,0 +1,180 @@
use std::{
process::{Child, Command},
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
};
/// A simple wrapper for `std::process::Command`
///
/// ## Spawn behavior
/// ### Unix
///
/// The spawned child process will get its own process group id, and it's going to foreground (by making stdin belong's to child's process group).
///
/// On drop, the calling process's group will become the foreground process group once again.
///
/// ### Windows
/// It does nothing special on windows system, `spawn` is the same as [std::process::Command::spawn](std::process::Command::spawn)
pub struct ForegroundProcess {
inner: Command,
pipeline_state: Arc<(AtomicU32, AtomicU32)>,
}
/// A simple wrapper for `std::process::Child`
///
/// It can only be created by `ForegroundProcess::spawn`.
pub struct ForegroundChild {
inner: Child,
pipeline_state: Arc<(AtomicU32, AtomicU32)>,
}
impl ForegroundProcess {
pub fn new(cmd: Command, pipeline_state: Arc<(AtomicU32, AtomicU32)>) -> Self {
Self {
inner: cmd,
pipeline_state,
}
}
pub fn spawn(&mut self) -> std::io::Result<ForegroundChild> {
let (ref pgrp, ref pcnt) = *self.pipeline_state;
let existing_pgrp = pgrp.load(Ordering::SeqCst);
fg_process_setup::prepare_to_foreground(&mut self.inner, existing_pgrp);
self.inner
.spawn()
.map(|child| {
fg_process_setup::set_foreground(&child, existing_pgrp);
let _ = pcnt.fetch_add(1, Ordering::SeqCst);
if existing_pgrp == 0 {
pgrp.store(child.id(), Ordering::SeqCst);
}
ForegroundChild {
inner: child,
pipeline_state: self.pipeline_state.clone(),
}
})
.map_err(|e| {
fg_process_setup::reset_foreground_id();
e
})
}
}
impl AsMut<Child> for ForegroundChild {
fn as_mut(&mut self) -> &mut Child {
&mut self.inner
}
}
impl Drop for ForegroundChild {
fn drop(&mut self) {
let (ref pgrp, ref pcnt) = *self.pipeline_state;
if pcnt.fetch_sub(1, Ordering::SeqCst) == 1 {
pgrp.store(0, Ordering::SeqCst);
fg_process_setup::reset_foreground_id()
}
}
}
// It's a simpler version of fish shell's external process handling.
#[cfg(target_family = "unix")]
mod fg_process_setup {
use nix::{
sys::signal,
unistd::{self, Pid},
};
use std::os::unix::prelude::{CommandExt, RawFd};
// TODO: when raising MSRV past 1.63.0, switch to OwnedFd
struct TtyHandle(RawFd);
impl Drop for TtyHandle {
fn drop(&mut self) {
let _ = unistd::close(self.0);
}
}
pub(super) fn prepare_to_foreground(
external_command: &mut std::process::Command,
existing_pgrp: u32,
) {
let tty = TtyHandle(unistd::dup(nix::libc::STDIN_FILENO).expect("dup"));
unsafe {
// Safety:
// POSIX only allows async-signal-safe functions to be called.
// `sigprocmask`, `setpgid` and `tcsetpgrp` are async-signal-safe according to:
// https://manpages.ubuntu.com/manpages/bionic/man7/signal-safety.7.html
external_command.pre_exec(move || {
// When this callback is run, std::process has already done:
// - pthread_sigmask(SIG_SETMASK) with an empty sigset
// - signal(SIGPIPE, SIG_DFL)
// However, we do need TTOU/TTIN blocked again during this setup.
let mut sigset = signal::SigSet::empty();
sigset.add(signal::Signal::SIGTSTP);
sigset.add(signal::Signal::SIGTTOU);
sigset.add(signal::Signal::SIGTTIN);
sigset.add(signal::Signal::SIGCHLD);
signal::sigprocmask(signal::SigmaskHow::SIG_BLOCK, Some(&sigset), None)
.expect("signal mask");
// According to glibc's job control manual:
// https://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html
// This has to be done *both* in the parent and here in the child due to race conditions.
set_foreground_pid(unistd::getpid(), existing_pgrp, tty.0);
// Now let the child process have all the signals by resetting with SIG_SETMASK.
let mut sigset = signal::SigSet::empty();
sigset.add(signal::Signal::SIGTSTP); // for now not really all: we don't support background jobs, so keep this one blocked
signal::sigprocmask(signal::SigmaskHow::SIG_SETMASK, Some(&sigset), None)
.expect("signal mask");
Ok(())
});
}
}
pub(super) fn set_foreground(process: &std::process::Child, existing_pgrp: u32) {
// called from the parent shell process - do the stdin tty check here
if atty::is(atty::Stream::Stdin) {
set_foreground_pid(
Pid::from_raw(process.id() as i32),
existing_pgrp,
nix::libc::STDIN_FILENO,
);
}
}
// existing_pgrp is 0 when we don't have an existing foreground process in the pipeline.
// Conveniently, 0 means "current pid" to setpgid. But not to tcsetpgrp.
fn set_foreground_pid(pid: Pid, existing_pgrp: u32, tty: RawFd) {
let _ = unistd::setpgid(pid, Pid::from_raw(existing_pgrp as i32));
let _ = unistd::tcsetpgrp(
tty,
if existing_pgrp == 0 {
pid
} else {
Pid::from_raw(existing_pgrp as i32)
},
);
}
/// Reset the foreground process group to the shell
pub(super) fn reset_foreground_id() {
if atty::is(atty::Stream::Stdin) {
if let Err(e) = nix::unistd::tcsetpgrp(nix::libc::STDIN_FILENO, unistd::getpgrp()) {
println!("ERROR: reset foreground id failed, tcsetpgrp result: {e:?}");
}
}
}
}
#[cfg(not(target_family = "unix"))]
mod fg_process_setup {
pub(super) fn prepare_to_foreground(_: &mut std::process::Command, _: u32) {}
pub(super) fn set_foreground(_: &std::process::Child, _: u32) {}
pub(super) fn reset_foreground_id() {}
}

View File

@ -1,3 +1,4 @@
mod foreground;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod linux;
#[cfg(target_os = "macos")]
@ -5,6 +6,7 @@ mod macos;
#[cfg(target_os = "windows")]
mod windows;
pub use self::foreground::{ForegroundChild, ForegroundProcess};
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::linux::*;
#[cfg(target_os = "macos")]

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
edition = "2021"
license = "MIT"
name = "nu-table"
version = "0.69.0"
version = "0.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
@ -14,7 +14,9 @@ path = "src/main.rs"
[dependencies]
nu-ansi-term = "0.46.0"
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
strip-ansi-escapes = "0.1.1"
atty = "0.2.14"
tabled = { version = "0.8.0", features = ["color"] }
tabled = { version = "0.9.0", features = ["color"], default-features = false }
json_to_table = { version = "0.1.0", features = ["color"] }
serde_json = "*"

View File

@ -1,8 +1,13 @@
mod nu_protocol_table;
mod table;
mod table_theme;
mod textstyle;
mod width_control;
pub use nu_protocol_table::NuTable;
pub use table::{Alignments, Table};
pub use table_theme::TableTheme;
pub use textstyle::{Alignment, StyledString, TextStyle};
pub use textstyle::{Alignment, TextStyle};
pub fn string_width(text: &str) -> usize {
tabled::papergrid::util::string_width_multiline_tab(text, 4)
}

View File

@ -1,6 +1,7 @@
use nu_protocol::Config;
use nu_table::{Alignments, StyledString, Table, TableTheme, TextStyle};
use nu_table::{Alignments, Table, TableTheme, TextStyle};
use std::collections::HashMap;
use tabled::papergrid::records::{cell_info::CellInfo, tcell::TCell};
fn main() {
let args: Vec<_> = std::env::args().collect();
@ -23,14 +24,23 @@ fn main() {
// The table rows
let rows = vec_of_str_to_vec_of_styledstr(&row_data, false);
// The table itself
let table = Table::new(headers, vec![rows; 3], TableTheme::rounded());
let count_cols = std::cmp::max(rows.len(), headers.len());
let mut rows = vec![rows; 3];
rows.insert(0, headers);
let table = Table::new(rows, (3, count_cols), width, true, false);
// FIXME: Config isn't available from here so just put these here to compile
let color_hm: HashMap<String, nu_ansi_term::Style> = HashMap::new();
// get the default config
let config = Config::default();
// Capture the table as a string
let output_table = table
.draw_table(&config, &color_hm, Alignments::default(), width)
.draw_table(
&config,
&color_hm,
Alignments::default(),
&TableTheme::rounded(),
width,
)
.unwrap_or_else(|| format!("Couldn't fit table into {} columns!", width));
// Draw the table
println!("{}", output_table)
@ -74,17 +84,23 @@ fn make_table_data() -> (Vec<&'static str>, Vec<&'static str>) {
(table_headers, row_data)
}
fn vec_of_str_to_vec_of_styledstr(data: &[&str], is_header: bool) -> Vec<StyledString> {
fn vec_of_str_to_vec_of_styledstr(
data: &[&str],
is_header: bool,
) -> Vec<TCell<CellInfo<'static>, TextStyle>> {
let mut v = vec![];
for x in data {
if is_header {
v.push(StyledString::new(
v.push(Table::create_cell(
String::from(*x),
TextStyle::default_header(),
))
} else {
v.push(StyledString::new(String::from(*x), TextStyle::basic_left()))
v.push(Table::create_cell(
String::from(*x),
TextStyle::basic_left(),
))
}
}
v

Some files were not shown because too many files have changed in this diff Show More