Compare commits

...

62 Commits

Author SHA1 Message Date
ebba89ea31 Bump to 0.20 (#2588) 2020-09-22 19:54:46 +12:00
8388afc9d9 add support for the text/csv content-type (#2587) 2020-09-22 15:26:20 +12:00
b133724b38 Add space between column suggestions (#2586) 2020-09-22 14:14:11 +12:00
09429d08aa Implement passthrough for benchmark (#2580)
* Implement passthrough for benchmark

Add a new option -p,--passthrough to benchmark.
With this option, the benchmark command prints its results to stdout and passes the block's output to the next command in the pipeline.

* Add execution block for benchmark -p

`benchmark --passthrough` now takes a block where the benchmark output is sent.
Example:
`benchmark -p {save bench.toml} {ls}`
2020-09-22 05:30:16 +12:00
9b577b8679 Update bigint/bigdecimal (#2585)
* Update bigint/bigdecimal

* clippy
2020-09-22 05:28:31 +12:00
7a595827f1 Fix subcommands column on help commands (#2584) 2020-09-21 19:57:26 +12:00
332e12ded0 Added test for ls -a (#2582) 2020-09-21 19:56:37 +12:00
a508e15efe CtrlD exits current shell (#2583) 2020-09-21 19:56:10 +12:00
a5b6bb6209 Add global mode to str trim (#2576)
* Add global mode to str trim

The global mode allows skipping non-string values,
and processes rows and tables as well

* Add tests to action with ActionMode::Global
2020-09-20 21:04:26 +12:00
1882a32b83 Context cleanup (#2581)
* Specialize 'Context' to EvaluationContext and CompletionContext

* Specialize 'Context' to EvaluationContext and CompletionContext

* fmt
2020-09-20 09:29:51 +12:00
798766b4b5 Remove panics from random integer and make the constraint more idiomatic (#2578)
* Remove panics from random integer and make the constraint more idiomatic

* Add open intervals to NumericRange
2020-09-20 08:41:49 +12:00
193c4cc6d5 Include subcommands in help commands (#2575)
* Add minor fixes to comments

* Include subcommands in `help commands`
2020-09-20 08:37:47 +12:00
422b6ca871 Add system, user and idle times to benchmark command (#2571)
* Add system, user and idle times to benchmark command

* Feature-gate dependency on heim in benchmark

* Reorder let bindings in benchmark

* Fully feature-gate rich-benchmark and print 0sec on zero duration
2020-09-20 05:13:14 +12:00
2b13ac3856 more table themes rounded and reinforced (#2579) 2020-09-19 12:10:34 -05:00
4c10351579 Exclude internal commands from 'help command' (#2573) 2020-09-19 11:48:30 +12:00
dd27aaef1b Limit open streaming to non-files, and files > 32mb (#2570) 2020-09-19 07:51:28 +12:00
6eb4a0e87b add replace all option to str find-replace (#2569) 2020-09-18 11:28:50 -05:00
15f3a545f0 Cleanup code in get and nu-value-ext (#2563)
* Cleanup code in get and nu-value-ext

* Remove unnecessary return statements from get
2020-09-18 18:40:20 +12:00
365f76ad19 Tidy up help command text (#2566) 2020-09-18 18:13:53 +12:00
df2845a9b4 implementing case-sensitive & case-insensitive completion matching (#2556)
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-09-17 10:52:58 -04:00
8453261211 Update rustyline to latest (#2565)
* Update rustyline to latest

* Go ahead and use rustyline for testing
2020-09-17 18:02:30 +12:00
1dc8f3300e Make the sleep command pass data through. (#2558) 2020-09-16 19:34:37 -04:00
10d4edc7af Slim down configuration readings and nu_cli clean up. (#2559)
We continue refactoring nu_cli and slim down a bit configuration
readings with a naive metadata `modified` field check.
2020-09-16 18:22:58 -05:00
50cbf91bc5 Remove trim in favor of str trim (#2560) 2020-09-16 15:59:32 -04:00
d05f9b3b1e Make the sleep command respond to Ctrl+C (#2550) 2020-09-16 12:14:33 -04:00
f5fad393d0 Refactor completion trait (#2555)
* Remove completion code from help/value shells

* Tweak the `Completer` trait for nushell.

Previously, this trait was built around rustyline's completion traits, and for
`Shell` instances. Now it is built for individual completers inside of nushell
that will complete a specific location based on a partial string. For example,
for completing a partially typed command in the command position.
2020-09-16 16:37:43 +12:00
d19a5f4c2f add a few more ansi escape sequences (#2553) 2020-09-15 16:01:57 -05:00
04451af776 Ctrl c exit issue (#2548)
* fix ctrl_c problem on windows

* updated cargo.lock
2020-09-15 09:59:51 -05:00
232aca76a4 Update rawkey for ARM support (#2547)
v0.1.2 of rawkey currently doesn't compile on ARM; v0.1.3 remedies this.
2020-09-15 11:20:32 +12:00
0178b53289 Core nu plugin load capability. (#2544)
We introduce the `plugin` nu sub command (`nu plugin`) with basic plugin
loading support. We can choose to load plugins from a directory. Originally
introduced to make integration tests faster (by not loading any plugins on startup at all)
but `nu plugin --load some_path ; test_pipeline_that_uses_plugins_just_loaded` does not see it.

Therefore, a `nu_with_plugins!` macro for tests was introduced on top of nu`s `--skip-plugins`
switch executable which is set to true when running the integration tests that use the `nu!` macro now..
2020-09-14 09:07:02 -05:00
e05e6b42fe Simplify a few boolean creations (#2543) 2020-09-14 13:15:44 +12:00
dd79afb503 Fix yanked crossterm version (#2542) 2020-09-14 13:14:59 +12:00
599bb9797d Implement exclusive and inclusive ranges with ..< and .. (#2541)
* Implement exclusive and inclusive ranges with .. and ..=

This commit adds right-exclusive ranges.

The original a..b inclusive syntax was changed to reflect the Rust notation.
New a..=b syntax was introduced to have the old behavior.

Currently, both a.. and b..= is valid, and it is unclear whether it's valid
to impose restrictions.

The original issue suggests .. for inclusive and ..< for exclusive ranges,
this can be implemented by making simple changes to this commit.

* Fix collect tests by changing ranges to ..=

* Fix clippy lints in exclusive range matching

* Implement exclusive ranges using `..<`
2020-09-14 09:53:08 +12:00
c355585112 Set active shell column to boolean (#2540) 2020-09-13 16:25:38 +12:00
45f32c9541 Allow math avg to work on durations (#2529)
* Allow `math avg` to work on durations

* formatting

* fix linting issue and implemented `math sum` for duration

* fix linting issue

* applied requested changes

* applied requested change for avg.rs

* formatting
2020-09-12 18:56:05 -05:00
7528094e12 Allow folding with tables. (#2538) 2020-09-12 01:40:52 -05:00
dcfa135ab9 support key-value argument in with-env(#2490) (#2530)
* support key-value argument in with-env

* remove unused import

* fix format for lint
2020-09-11 18:17:35 +12:00
e9bb4f25eb accept multiple variables in with-env(#2490) (#2526)
* accept multiple variables in with-env(#2490)

* add examples for test

* fix format(#2490)
2020-09-10 19:23:28 +12:00
0f7a9bbd31 Further clarify duration conversion (#2522) 2020-09-10 15:33:37 +12:00
73e65df5f6 Fix path completions for cd command. (#2525)
Previously, we weren't expanding `~`, so `std::fs::metadata` was failing. We now
make use of `PathSuggestion` to get the actual path, as represented by a
`PathBuf`.
2020-09-09 18:32:20 -04:00
a63a5adafa remove unused dependencies (#2520)
* remove unused dependencies

* moved umask to cfg(unix)

* changed Inflector to inflector, hoping it fixes the issue.

* roll back Inflector

* removed commented out deps now that everything looks good.
2020-09-09 13:57:51 -05:00
2eb4f8d28a updated dependencies (#2517) 2020-09-09 10:35:45 +12:00
d9ae66791a allow decimals as a range boundary (#2509) 2020-09-08 05:30:11 +12:00
2c5939dc7d each group and each window subcommands. (#2508)
* First commit updating `config` to use subcommands (#2119)
    - Implemented `get` subcommand

* Implmented `config set` as a subcommand.

* Implemented `config set_into` as subcommand

* Fixed base `config` command
 - Instead of outputting help, it now outputs the list of all
 configuration parameters.

* Added `config clear` subcommand

* Added `config load` and `config remove` subcommands

* Added `config path` subcommand

* fixed clippy

* initial commit for implementing groups

* each group works

* each group is slightly cleaner + added example

* Added `each window` subcommand
    - No support for stride flag yet

* each window stride implemented

* Added tests and minor documentation changes

* fixed clippy

* fixed clippy again
2020-09-07 17:54:52 +12:00
3150e70fc7 Add cpu time to ps -l (#2507) 2020-09-07 17:02:45 +12:00
c9ffd6afc0 Improve range parsing and handling (#2506)
* Improve range parsing and handling

* linting
2020-09-07 14:43:58 +12:00
986b427038 Add modulo operator and simplify in/not-in (#2505) 2020-09-07 12:12:55 +12:00
c973850571 Show completions more than one level below ~ (#2503)
Previously, we'd check for a `~/` prefix and then return the home directory as
the base `PathBuf`. We failed to consider pushing any of the other possible path
components into this base dir, which prevent completions more than one level
deep (for example, `~/.config/<TAB>` would fail).
2020-09-06 20:06:13 -04:00
5a725f9651 Num links added to ls -l output (#2496) 2020-09-06 12:36:50 -04:00
79cc725aff Interpreting ranges for substring (#2499) 2020-09-06 12:35:11 -04:00
e2cbc4e853 Weather symbol cleanup (#2502)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* clean up some comments
2020-09-05 14:52:49 -05:00
b5a27f0ccb pr to get my main in sync (#2501)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges
2020-09-05 13:13:34 -05:00
bdb12f4bff Weather chars (#2500)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* added weather symbols/chars
2020-09-05 13:13:07 -05:00
c9c29f9e4c Remove space from command name completion suggestion. (#2498)
Although convenient, since the user doesn't have to type the space, it could be
a little surprising to users since they may think that was the only completion
in certain completions modes (for example, `cycle`).
2020-09-05 15:15:20 +12:00
32951f1161 implement exec for unix platforms (#2495) 2020-09-04 22:27:01 -04:00
56f85b3108 Provide path as part of the suggestion from the path completer. (#2497) 2020-09-04 22:10:26 -04:00
16f85f32a2 ls **/* does not show hidden files without the -a flag (#2407)
* fixed: .*.(ext|*)

* ls **/* does not return hidden files without the -a flag

* fixed formatting

* fixed clippy issues

* fixed clippy issues, v2

* added `#[cfg(unix)]` to windows-failing test
2020-09-05 07:32:58 +12:00
2ae2f2ea9d Ensure ansi mode windows (#2494)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* ensure ansi mode is enabled on windows
ansi mode sometimes gets out of sync in Windows.
I'm not sure why but this appears to fix it.
2020-09-04 14:24:46 -05:00
4696c9069b use fs_extra to recursively move folders (#2487) 2020-09-04 11:44:53 +12:00
1ffbb66e64 Initial implementation of random integer subcommand. (#2489)
* Initial implementation of random integer subcommand.

* Added additional examples.

Co-authored-by: Stacy Maydew <stacy.maydew@starlab.io>
2020-09-04 07:23:02 +12:00
8dc7b8a7cd Update clipboard feature (#2491)
* WIP - compiling but not working

* semi-working

* making progress

* working except for table lines

* fmt + clippy

* cleaned up some comments

* working line colors

* fmt, clippy, updated sample config.toml

* merges

* shouldn't these both bet clipboard-cli?
2020-09-04 07:21:32 +12:00
666e6a7b57 Size: count unicode graphmemes as single char (#2482) 2020-09-03 04:54:00 +12:00
216 changed files with 4959 additions and 2909 deletions

View File

@ -71,7 +71,7 @@ steps:
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used - bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
condition: eq(variables['style'], 'canary') condition: eq(variables['style'], 'canary')
displayName: Check clippy lints displayName: Check clippy lints
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features - bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
condition: eq(variables['style'], 'minimal') condition: eq(variables['style'], 'minimal')
displayName: Run tests displayName: Run tests
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra - bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra

653
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu" name = "nu"
readme = "README.md" readme = "README.md"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
version = "0.19.0" version = "0.20.0"
[workspace] [workspace]
members = ["crates/*/"] members = ["crates/*/"]
@ -18,48 +18,48 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"} nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
nu-data = {version = "0.19.0", path = "./crates/nu-data"} nu-data = {version = "0.20.0", path = "./crates/nu-data"}
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"} nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"} nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"} nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"} nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.19.0", path = "./crates/nu-source"} nu-source = {version = "0.20.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"} nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
nu_plugin_binaryview = {version = "0.19.0", path = "./crates/nu_plugin_binaryview", optional = true} nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true} nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.19.0", path = "./crates/nu_plugin_from_bson", optional = true} nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_from_sqlite", optional = true} nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.19.0", path = "./crates/nu_plugin_inc", optional = true} nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true} nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true} nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true} nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true} nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true} nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true} nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true} nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.19.0", path = "./crates/nu_plugin_to_bson", optional = true} nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_to_sqlite", optional = true} nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
nu_plugin_tree = {version = "0.19.0", path = "./crates/nu_plugin_tree", optional = true} nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
crossterm = {version = "0.17.5", optional = true} crossterm = {version = "0.17", optional = true}
semver = {version = "0.10.0", optional = true} semver = {version = "0.10.0", optional = true}
url = {version = "2.1.1", optional = true} url = {version = "2.1.1", optional = true}
clap = "2.33.1" clap = "2.33.3"
ctrlc = "3.1.4" ctrlc = "3.1.6"
dunce = "1.0.1" dunce = "1.0.1"
futures = {version = "0.3", features = ["compat", "io-compat"]} futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.8" log = "0.4.11"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
quick-xml = "0.18.1" quick-xml = "0.18.1"
[dev-dependencies] [dev-dependencies]
nu-test-support = {version = "0.19.0", path = "./crates/nu-test-support"} nu-test-support = {version = "0.20.0", path = "./crates/nu-test-support"}
[build-dependencies] [build-dependencies]
serde = {version = "1.0.110", features = ["derive"]} serde = {version = "1.0.115", features = ["derive"]}
toml = "0.5.6" toml = "0.5.6"
[features] [features]
@ -75,9 +75,11 @@ default = [
"ptree-support", "ptree-support",
"term-support", "term-support",
"uuid-support", "uuid-support",
"rustyline-support",
"match", "match",
"post", "post",
"fetch", "fetch",
"rich-benchmark",
] ]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"] extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
stable = ["default"] stable = ["default"]
@ -105,6 +107,8 @@ ctrlc-support = ["nu-cli/ctrlc"]
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"] directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
git-support = ["nu-cli/git2"] git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"] ptree-support = ["nu-cli/ptree"]
rich-benchmark = ["nu-cli/rich-benchmark"]
rustyline-support = ["nu-cli/rustyline-support"]
term-support = ["nu-cli/term"] term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"] trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"] uuid-support = ["nu-cli/uuid_crate"]

View File

@ -4,103 +4,102 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.19.0" version = "0.20.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-data = {version = "0.19.0", path = "../nu-data"} nu-data = {version = "0.20.0", path = "../nu-data"}
nu-errors = {version = "0.19.0", path = "../nu-errors"} nu-errors = {version = "0.20.0", path = "../nu-errors"}
nu-parser = {version = "0.19.0", path = "../nu-parser"} nu-parser = {version = "0.20.0", path = "../nu-parser"}
nu-plugin = {version = "0.19.0", path = "../nu-plugin"} nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
nu-protocol = {version = "0.19.0", path = "../nu-protocol"} nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
nu-source = {version = "0.19.0", path = "../nu-source"} nu-source = {version = "0.20.0", path = "../nu-source"}
nu-table = {version = "0.19.0", path = "../nu-table"} nu-table = {version = "0.20.0", path = "../nu-table"}
nu-test-support = {version = "0.19.0", path = "../nu-test-support"} nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"} nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
ansi_term = "0.12.1" ansi_term = "0.12.1"
app_dirs = {version = "2", package = "app_dirs2"}
async-recursion = "0.3.1" async-recursion = "0.3.1"
async-trait = "0.1.36" async-trait = "0.1.40"
base64 = "0.12.3" base64 = "0.12.3"
bigdecimal = {version = "0.1.2", features = ["serde"]} bigdecimal = {version = "0.2.0", features = ["serde"]}
byte-unit = "3.1.3" byte-unit = "4.0.9"
bytes = "0.5.5" bytes = "0.5.6"
calamine = "0.16" calamine = "0.16.1"
chrono = {version = "0.4.11", features = ["serde"]} chrono = {version = "0.4.15", features = ["serde"]}
clap = "2.33.1" clap = "2.33.3"
codespan-reporting = "0.9.5" codespan-reporting = "0.9.5"
csv = "1.1" csv = "1.1.3"
ctrlc = {version = "3.1.4", optional = true} ctrlc = {version = "3.1.6", optional = true}
derive-new = "0.5.8" derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true} directories = {version = "3.0.1", optional = true}
dirs = {version = "2.0.2", optional = true} dirs = {version = "3.0.1", optional = true}
dtparse = "1.1.0" dtparse = "1.1.0"
dunce = "1.0.1" dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
filesize = "0.2.0" filesize = "0.2.0"
futures = {version = "0.3", features = ["compat", "io-compat"]} fs_extra = "1.2.0"
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
futures-util = "0.3.5" futures-util = "0.3.5"
futures_codec = "0.4" futures_codec = "0.4.1"
getset = "0.1.1" getset = "0.1.1"
git2 = {version = "0.13.6", default_features = false, optional = true} git2 = {version = "0.13.11", default_features = false, optional = true}
glob = "0.3.0" glob = "0.3.0"
hex = "0.4" heim = {version = "0.1.0-beta.3", optional = true}
htmlescape = "0.3.1" htmlescape = "0.3.1"
ical = "0.6.*" ical = "0.6.0"
ichwh = {version = "0.3.4", optional = true} ichwh = {version = "0.3.4", optional = true}
indexmap = {version = "1.4.0", features = ["serde-1"]} indexmap = {version = "1.6.0", features = ["serde-1"]}
itertools = "0.9.0" itertools = "0.9.0"
log = "0.4.8" log = "0.4.11"
meval = "0.2" meval = "0.2.0"
natural = "0.5.0" natural = "0.5.0"
num-bigint = {version = "0.2.6", features = ["serde"]} num-bigint = {version = "0.3.0", features = ["serde"]}
num-format = {version = "0.4", features = ["with-num-bigint"]} num-format = {version = "0.4.0", features = ["with-num-bigint"]}
num-traits = "0.2.11" num-traits = "0.2.12"
parking_lot = "0.11.0" parking_lot = "0.11.0"
pin-utils = "0.1.0" pin-utils = "0.1.0"
pretty-hex = "0.1.1" pretty-hex = "0.2.0"
pretty_env_logger = "0.4.0" ptree = {version = "0.3.0", optional = true}
ptree = {version = "0.2", optional = true}
query_interface = "0.3.5" query_interface = "0.3.5"
rand = "0.7" rand = "0.7.3"
regex = "1" regex = "1.3.9"
roxmltree = "0.13.0" roxmltree = "0.13.0"
rust-embed = "5.6.0" rust-embed = "5.6.0"
rustyline = "6.2.0" rustyline = {version = "6.3.0", optional = true}
serde = {version = "1.0.114", features = ["derive"]} serde = {version = "1.0.115", features = ["derive"]}
serde-hjson = "0.9.1" serde-hjson = "0.9.1"
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
serde_json = "1.0.55" serde_json = "1.0.57"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.7.0"
serde_yaml = "0.8" serde_yaml = "0.8.13"
sha2 = "0.9.1" sha2 = "0.9.1"
shellexpand = "2.0.0" shellexpand = "2.0.0"
strip-ansi-escapes = "0.1.0" strip-ansi-escapes = "0.1.0"
tempfile = "3.1.0" tempfile = "3.1.0"
term = {version = "0.5.2", optional = true} term = {version = "0.6.1", optional = true}
term_size = "0.3.2" term_size = "0.3.2"
termcolor = "1.1.0" termcolor = "1.1.0"
toml = "0.5.6" toml = "0.5.6"
typetag = "0.1.5" unicode-segmentation = "1.6.0"
umask = "1.0.0" uom = {version = "0.28.0", features = ["f64", "try-from"]}
unicode-xid = "0.2.1"
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true} uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
which = {version = "4.0.2", optional = true} which = {version = "4.0.2", optional = true}
zip = {version = "0.5.6", optional = true} zip = {version = "0.5.7", optional = true}
clipboard = {version = "0.5", optional = true}
encoding_rs = "0.8.23"
quick-xml = "0.18.1"
rayon = "1.3.1"
trash = {version = "1.0.1", optional = true}
url = {version = "2.1.1"}
Inflector = "0.11" Inflector = "0.11"
clipboard = {version = "0.5.0", optional = true}
encoding_rs = "0.8.24"
quick-xml = "0.18.1"
rayon = "1.4.0"
trash = {version = "1.1.1", optional = true}
url = "2.1.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
umask = "1.0.0"
users = "0.10.0" users = "0.10.0"
# TODO this will be possible with new dependency resolver # TODO this will be possible with new dependency resolver
@ -112,17 +111,18 @@ users = "0.10.0"
[dependencies.rusqlite] [dependencies.rusqlite]
features = ["bundled", "blob"] features = ["bundled", "blob"]
optional = true optional = true
version = "0.23.1" version = "0.24.0"
[build-dependencies] [build-dependencies]
git2 = {version = "0.13", optional = true} git2 = {version = "0.13.11", optional = true}
[dev-dependencies] [dev-dependencies]
quickcheck = "0.9" quickcheck = "0.9.2"
quickcheck_macros = "0.9" quickcheck_macros = "0.9.1"
[features] [features]
clipboard-cli = ["clipboard"] clipboard-cli = ["clipboard"]
rich-benchmark = ["heim"]
rustyline-support = ["rustyline"]
stable = [] stable = []
trash-support = ["trash"] trash-support = ["trash"]

View File

@ -1,38 +1,30 @@
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary}; use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
use crate::context::Context; use crate::evaluation_context::EvaluationContext;
use crate::git::current_branch;
use crate::path::canonicalize; use crate::path::canonicalize;
use crate::prelude::*; use crate::prelude::*;
#[cfg(feature = "rustyline-support")]
use crate::shell::Helper; use crate::shell::Helper;
use crate::EnvironmentSyncer; use crate::EnvironmentSyncer;
use futures_codec::FramedRead; use futures_codec::FramedRead;
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError}; use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments}; use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use log::{debug, trace}; use log::{debug, trace};
use rustyline::config::{ColorMode, CompletionType, Config}; #[cfg(feature = "rustyline-support")]
use rustyline::error::ReadlineError; use rustyline::{
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word}; self,
config::Configurer,
config::{ColorMode, CompletionType, Config},
error::ReadlineError,
At, Cmd, Editor, KeyPress, Movement, Word,
};
use std::error::Error; use std::error::Error;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
if let Ok(plugins) = crate::plugin::scan() {
context.add_commands(
plugins
.into_iter()
.filter(|p| !context.is_command_registered(p.name()))
.collect(),
);
}
Ok(())
}
pub fn search_paths() -> Vec<std::path::PathBuf> { pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env; use std::env;
@ -64,21 +56,16 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths search_paths
} }
pub fn create_default_context( pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
syncer: &mut crate::EnvironmentSyncer, let mut context = EvaluationContext::basic()?;
interactive: bool,
) -> Result<Context, Box<dyn Error>> {
syncer.load_environment();
let mut context = Context::basic()?;
syncer.sync_env_vars(&mut context);
syncer.sync_path_vars(&mut context);
{ {
use crate::commands::*; use crate::commands::*;
context.add_commands(vec![ context.add_commands(vec![
whole_stream_command(NuPlugin),
// System/file operations // System/file operations
whole_stream_command(Exec),
whole_stream_command(Pwd), whole_stream_command(Pwd),
whole_stream_command(Ls), whole_stream_command(Ls),
whole_stream_command(Du), whole_stream_command(Du),
@ -136,7 +123,6 @@ pub fn create_default_context(
whole_stream_command(SplitRow), whole_stream_command(SplitRow),
whole_stream_command(SplitChars), whole_stream_command(SplitChars),
whole_stream_command(Lines), whole_stream_command(Lines),
whole_stream_command(Trim),
whole_stream_command(Echo), whole_stream_command(Echo),
whole_stream_command(Parse), whole_stream_command(Parse),
whole_stream_command(Str), whole_stream_command(Str),
@ -204,6 +190,8 @@ pub fn create_default_context(
whole_stream_command(Rename), whole_stream_command(Rename),
whole_stream_command(Uniq), whole_stream_command(Uniq),
whole_stream_command(Each), whole_stream_command(Each),
whole_stream_command(EachGroup),
whole_stream_command(EachWindow),
whole_stream_command(IsEmpty), whole_stream_command(IsEmpty),
// Table manipulation // Table manipulation
whole_stream_command(Move), whole_stream_command(Move),
@ -265,6 +253,7 @@ pub fn create_default_context(
whole_stream_command(RandomDice), whole_stream_command(RandomDice),
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID), whole_stream_command(RandomUUID),
whole_stream_command(RandomInteger),
// Path // Path
whole_stream_command(PathBasename), whole_stream_command(PathBasename),
whole_stream_command(PathCommand), whole_stream_command(PathCommand),
@ -282,7 +271,7 @@ pub fn create_default_context(
whole_stream_command(UrlQuery), whole_stream_command(UrlQuery),
]); ]);
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard-cli")]
{ {
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]); context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
} }
@ -295,284 +284,76 @@ pub async fn run_vec_of_pipelines(
pipelines: Vec<String>, pipelines: Vec<String>,
redirect_stdin: bool, redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let mut syncer = crate::EnvironmentSyncer::new(); let mut syncer = EnvironmentSyncer::new();
let mut context = create_default_context(&mut syncer, false)?; let mut context = create_default_context(false)?;
let config = syncer.get_config();
let _ = register_plugins(&mut context); context.configure(&config, |_, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
#[cfg(feature = "ctrlc")] if let Err(reason) = syncer.autoenv(ctx) {
{ print_err(reason, &Text::from(""));
let cc = context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
}
} }
// before we start up, let's run our startup commands let _ = register_plugins(ctx);
if let Ok(config) = nu_data::config::config(Tag::unknown()) { let _ = configure_ctrl_c(ctx);
if let Some(commands) = config.get("startup") { });
match commands {
Value { let _ = run_startup_commands(&mut context, &config).await;
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ = run_pipeline_standalone(
pipeline_string,
false,
&mut context,
false,
)
.await;
}
}
}
_ => {
println!("warning: expected a table of pipeline strings as startup commands");
}
}
}
}
for pipeline in pipelines { for pipeline in pipelines {
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?; run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
} }
Ok(())
}
pub async fn run_pipeline_standalone(
pipeline: String,
redirect_stdin: bool,
context: &mut Context,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
match line {
LineResult::Success(line) => {
let error_code = {
let errors = context.current_errors.clone();
let errors = errors.lock();
if errors.len() > 0 {
1
} else {
0
}
};
context.maybe_print_errors(Text::from(line));
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
context.with_host(|_host| {
print_err(err, &Text::from(line.clone()));
});
context.maybe_print_errors(Text::from(line));
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
}
Ok(()) Ok(())
} }
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) { #[cfg(feature = "rustyline-support")]
#[cfg(windows)] fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular; match input {
#[cfg(not(windows))] Ok(s) => LineResult::Success(s),
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List; Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::CtrlD,
let config = Config::builder().color_mode(ColorMode::Forced).build(); Err(err) => {
let mut rl: Editor<_> = Editor::with_config(config); outln!("Error: {:?}", err);
LineResult::Break
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
rl.bind_sequence(
KeyPress::ControlLeft,
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
);
rl.bind_sequence(
KeyPress::ControlRight,
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
);
// Let's set the defaults up front and then override them later if the user indicates
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
rl.set_max_history_size(100);
rl.set_history_ignore_dups(true);
rl.set_history_ignore_space(false);
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
rl.set_completion_prompt_limit(100);
rl.set_keyseq_timeout(-1);
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
rl.set_auto_add_history(false);
rl.set_bell_style(rustyline::config::BellStyle::default());
rl.set_color_mode(rustyline::ColorMode::Enabled);
rl.set_tab_stop(8);
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
println!("Error loading keybindings: {:?}", e);
}
let config = match config::config(Tag::unknown()) {
Ok(config) => config,
Err(e) => {
eprintln!("Config could not be loaded.");
if let ShellError {
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
..
} = e
{
eprintln!("{}", diagnostic.message);
}
IndexMap::new()
}
};
if let Ok(config) = config::config(Tag::unknown()) {
if let Some(line_editor_vars) = config.get("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
match idx.as_ref() {
"max_history_size" => {
if let Ok(max_history_size) = value.as_u64() {
rl.set_max_history_size(max_history_size as usize);
} }
} }
"history_duplicates" => {
// history_duplicates = match value.as_string() {
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
// rustyline::config::HistoryDuplicates::AlwaysAdd
// }
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
// }
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
// };
if let Ok(history_duplicates) = value.as_bool() {
rl.set_history_ignore_dups(history_duplicates);
}
}
"history_ignore_space" => {
if let Ok(history_ignore_space) = value.as_bool() {
rl.set_history_ignore_space(history_ignore_space);
}
}
"completion_type" => {
let completion_type = match value.as_string() {
Ok(s) if s.to_lowercase() == "circular" => {
rustyline::config::CompletionType::Circular
}
Ok(s) if s.to_lowercase() == "list" => {
rustyline::config::CompletionType::List
}
#[cfg(all(unix, feature = "with-fuzzy"))]
Ok(s) if s.to_lowercase() == "fuzzy" => {
rustyline::config::CompletionType::Fuzzy
}
_ => DEFAULT_COMPLETION_MODE,
};
rl.set_completion_type(completion_type);
}
"completion_prompt_limit" => {
if let Ok(completion_prompt_limit) = value.as_u64() {
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
}
}
"keyseq_timeout_ms" => {
if let Ok(keyseq_timeout_ms) = value.as_u64() {
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
}
}
"edit_mode" => {
let edit_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
Ok(s) if s.to_lowercase() == "emacs" => {
rustyline::config::EditMode::Emacs
}
_ => rustyline::config::EditMode::Emacs,
};
rl.set_edit_mode(edit_mode);
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
// no matter what you may have configured. This is so that key chords
// can be applied without having to do them in a given timeout. So,
// it essentially turns off the keyseq timeout.
}
"auto_add_history" => {
if let Ok(auto_add_history) = value.as_bool() {
rl.set_auto_add_history(auto_add_history);
}
}
"bell_style" => {
let bell_style = match value.as_string() {
Ok(s) if s.to_lowercase() == "audible" => {
rustyline::config::BellStyle::Audible
}
Ok(s) if s.to_lowercase() == "none" => {
rustyline::config::BellStyle::None
}
Ok(s) if s.to_lowercase() == "visible" => {
rustyline::config::BellStyle::Visible
}
_ => rustyline::config::BellStyle::default(),
};
rl.set_bell_style(bell_style);
}
"color_mode" => {
let color_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
Ok(s) if s.to_lowercase() == "disabled" => {
rustyline::ColorMode::Disabled
}
_ => rustyline::ColorMode::Enabled,
};
rl.set_color_mode(color_mode);
}
"tab_stop" => {
if let Ok(tab_stop) = value.as_u64() {
rl.set_tab_stop(tab_stop as usize);
}
}
_ => (),
}
}
}
}
(rl, config)
} }
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input. /// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
pub async fn cli( #[cfg(feature = "rustyline-support")]
mut syncer: EnvironmentSyncer, pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
mut context: Context, let mut syncer = EnvironmentSyncer::new();
) -> Result<(), Box<dyn Error>> { let configuration = syncer.get_config();
let configuration = nu_data::config::NuConfig::new();
let mut rl = default_rustyline_editor_configuration();
context.configure(&configuration, |config, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
}
let _ = configure_ctrl_c(ctx);
let _ = configure_rustyline_editor(&mut rl, config);
let helper = Some(nu_line_editor_helper(ctx, config));
rl.set_helper(helper);
});
let _ = run_startup_commands(&mut context, &configuration).await;
let history_path = crate::commands::history::history_path(&configuration); let history_path = crate::commands::history::history_path(&configuration);
let _ = register_plugins(&mut context);
let (mut rl, config) = create_rustyline_configuration();
// we are ok if history does not exist
let _ = rl.load_history(&history_path); let _ = rl.load_history(&history_path);
let skip_welcome_message = config let skip_welcome_message = configuration
.get("skip_welcome_message") .var("skip_welcome_message")
.map(|x| x.is_true()) .map(|x| x.is_true())
.unwrap_or(false); .unwrap_or(false);
if !skip_welcome_message { if !skip_welcome_message {
@ -587,44 +368,8 @@ pub async fn cli(
let _ = ansi_term::enable_ansi_support(); let _ = ansi_term::enable_ansi_support();
} }
#[cfg(feature = "ctrlc")]
{
let cc = context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
}
let mut ctrlcbreak = false; let mut ctrlcbreak = false;
// before we start up, let's run our startup commands
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ = run_pipeline_standalone(
pipeline_string,
false,
&mut context,
false,
)
.await;
}
}
}
_ => {
println!("warning: expected a table of pipeline strings as startup commands");
}
}
}
}
loop { loop {
if context.ctrl_c.load(Ordering::SeqCst) { if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst); context.ctrl_c.store(false, Ordering::SeqCst);
@ -633,12 +378,8 @@ pub async fn cli(
let cwd = context.shell_manager.path(); let cwd = context.shell_manager.path();
let hinter = init_hinter(&config);
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
let colored_prompt = { let colored_prompt = {
if let Some(prompt) = config.get("prompt") { if let Some(prompt) = configuration.var("prompt") {
let prompt_line = prompt.as_string()?; let prompt_line = prompt.as_string()?;
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) { match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
@ -695,6 +436,7 @@ pub async fn cli(
} }
} }
} else { } else {
use crate::git::current_branch;
format!( format!(
"\x1b[32m{}{}\x1b[m> ", "\x1b[32m{}{}\x1b[m> ",
cwd, cwd,
@ -722,14 +464,28 @@ pub async fn cli(
initial_command = None; initial_command = None;
} }
let line = process_line(readline, &mut context, false, true).await; let line = match convert_rustyline_result_to_string(readline) {
LineResult::Success(s) => process_line(&s, &mut context, false, true).await,
x => x,
};
// Check the config to see if we need to update the path // Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call // TODO: make sure config is cached so we don't path this load every call
// FIXME: we probably want to be a bit more graceful if we can't set the environment // FIXME: we probably want to be a bit more graceful if we can't set the environment
context.configure(&configuration, |config, ctx| {
if syncer.did_config_change() {
syncer.reload(); syncer.reload();
syncer.sync_env_vars(&mut context); syncer.sync_env_vars(ctx);
syncer.sync_path_vars(&mut context); syncer.sync_path_vars(ctx);
}
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
}
let _ = configure_rustyline_editor(&mut rl, config);
});
match line { match line {
LineResult::Success(line) => { LineResult::Success(line) => {
@ -752,7 +508,7 @@ pub async fn cli(
LineResult::CtrlC => { LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())? let config_ctrlc_exit = config::config(Tag::unknown())?
.get("ctrlc_exit") .get("ctrlc_exit")
.map(|s| s.value.expect_string() == "true") .map(|s| s.value.is_true())
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells .unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
if !config_ctrlc_exit { if !config_ctrlc_exit {
@ -769,6 +525,13 @@ pub async fn cli(
} }
} }
LineResult::CtrlD => {
context.shell_manager.remove_at_current();
if context.shell_manager.is_empty() {
break;
}
}
LineResult::Break => { LineResult::Break => {
break; break;
} }
@ -782,15 +545,283 @@ pub async fn cli(
Ok(()) Ok(())
} }
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> { pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
// Show hints unless explicitly disabled in config if let Ok(plugins) = crate::plugin::scan(search_paths()) {
if let Some(line_editor_vars) = config.get("line_editor") { context.add_commands(
plugins
.into_iter()
.filter(|p| !context.is_command_registered(p.name()))
.collect(),
);
}
Ok(())
}
fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(feature = "ctrlc")]
{
let cc = _context.ctrl_c.clone();
ctrlc::set_handler(move || {
cc.store(true, Ordering::SeqCst);
})?;
if _context.ctrl_c.load(Ordering::SeqCst) {
_context.ctrl_c.store(false, Ordering::SeqCst);
}
}
Ok(())
}
async fn run_startup_commands(
context: &mut EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> Result<(), ShellError> {
if let Some(commands) = config.var("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ =
run_pipeline_standalone(pipeline_string, false, context, false).await;
}
}
}
_ => {
return Err(ShellError::untagged_runtime_error(
"expected a table of pipeline strings as startup commands",
))
}
}
}
Ok(())
}
pub async fn run_pipeline_standalone(
pipeline: String,
redirect_stdin: bool,
context: &mut EvaluationContext,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
let line = process_line(&pipeline, context, redirect_stdin, false).await;
match line {
LineResult::Success(line) => {
let error_code = {
let errors = context.current_errors.clone();
let errors = errors.lock();
if errors.len() > 0 {
1
} else {
0
}
};
context.maybe_print_errors(Text::from(line));
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
context.with_host(|_host| {
print_err(err, &Text::from(line.clone()));
});
context.maybe_print_errors(Text::from(line));
if exit_on_error {
std::process::exit(1);
}
}
_ => {}
}
Ok(())
}
#[cfg(feature = "rustyline-support")]
fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let config = Config::builder().color_mode(ColorMode::Forced).build();
let mut rl: Editor<_> = Editor::with_config(config);
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
rl.bind_sequence(
KeyPress::ControlLeft,
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
);
rl.bind_sequence(
KeyPress::ControlRight,
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
);
// Let's set the defaults up front and then override them later if the user indicates
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
rl.set_max_history_size(100);
rl.set_history_ignore_dups(true);
rl.set_history_ignore_space(false);
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
rl.set_completion_prompt_limit(100);
rl.set_keyseq_timeout(-1);
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
rl.set_auto_add_history(false);
rl.set_bell_style(rustyline::config::BellStyle::default());
rl.set_color_mode(rustyline::ColorMode::Enabled);
rl.set_tab_stop(8);
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
println!("Error loading keybindings: {:?}", e);
}
rl
}
#[cfg(feature = "rustyline-support")]
fn configure_rustyline_editor(
rl: &mut Editor<Helper>,
config: &dyn nu_data::config::Conf,
) -> Result<(), ShellError> {
#[cfg(windows)]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
#[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
if let Some(line_editor_vars) = config.var("line_editor") {
for (idx, value) in line_editor_vars.row_entries() {
match idx.as_ref() {
"max_history_size" => {
if let Ok(max_history_size) = value.as_u64() {
rl.set_max_history_size(max_history_size as usize);
}
}
"history_duplicates" => {
// history_duplicates = match value.as_string() {
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
// rustyline::config::HistoryDuplicates::AlwaysAdd
// }
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
// }
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
// };
if let Ok(history_duplicates) = value.as_bool() {
rl.set_history_ignore_dups(history_duplicates);
}
}
"history_ignore_space" => {
if let Ok(history_ignore_space) = value.as_bool() {
rl.set_history_ignore_space(history_ignore_space);
}
}
"completion_type" => {
let completion_type = match value.as_string() {
Ok(s) if s.to_lowercase() == "circular" => {
rustyline::config::CompletionType::Circular
}
Ok(s) if s.to_lowercase() == "list" => {
rustyline::config::CompletionType::List
}
#[cfg(all(unix, feature = "with-fuzzy"))]
Ok(s) if s.to_lowercase() == "fuzzy" => {
rustyline::config::CompletionType::Fuzzy
}
_ => DEFAULT_COMPLETION_MODE,
};
rl.set_completion_type(completion_type);
}
"completion_prompt_limit" => {
if let Ok(completion_prompt_limit) = value.as_u64() {
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
}
}
"keyseq_timeout_ms" => {
if let Ok(keyseq_timeout_ms) = value.as_u64() {
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
}
}
"edit_mode" => {
let edit_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
_ => rustyline::config::EditMode::Emacs,
};
rl.set_edit_mode(edit_mode);
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
// no matter what you may have configured. This is so that key chords
// can be applied without having to do them in a given timeout. So,
// it essentially turns off the keyseq timeout.
}
"auto_add_history" => {
if let Ok(auto_add_history) = value.as_bool() {
rl.set_auto_add_history(auto_add_history);
}
}
"bell_style" => {
let bell_style = match value.as_string() {
Ok(s) if s.to_lowercase() == "audible" => {
rustyline::config::BellStyle::Audible
}
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
Ok(s) if s.to_lowercase() == "visible" => {
rustyline::config::BellStyle::Visible
}
_ => rustyline::config::BellStyle::default(),
};
rl.set_bell_style(bell_style);
}
"color_mode" => {
let color_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
_ => rustyline::ColorMode::Enabled,
};
rl.set_color_mode(color_mode);
}
"tab_stop" => {
if let Ok(tab_stop) = value.as_u64() {
rl.set_tab_stop(tab_stop as usize);
}
}
_ => (),
}
}
}
Ok(())
}
#[cfg(feature = "rustyline-support")]
fn nu_line_editor_helper(
context: &mut EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> crate::shell::Helper {
let hinter = rustyline_hinter(config);
crate::shell::Helper::new(context.clone(), hinter)
}
#[cfg(feature = "rustyline-support")]
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
if let Some(line_editor_vars) = config.var("line_editor") {
for (idx, value) in line_editor_vars.row_entries() { for (idx, value) in line_editor_vars.row_entries() {
if idx == "show_hints" && value.expect_string() == "false" { if idx == "show_hints" && value.expect_string() == "false" {
return None; return None;
} }
} }
} }
Some(rustyline::hint::HistoryHinter {}) Some(rustyline::hint::HistoryHinter {})
} }
@ -806,11 +837,12 @@ fn chomp_newline(s: &str) -> &str {
pub enum LineResult { pub enum LineResult {
Success(String), Success(String),
Error(String, ShellError), Error(String, ShellError),
CtrlC,
Break, Break,
CtrlC,
CtrlD,
} }
pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, ShellError> { pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
let line = if line.ends_with('\n') { let line = if line.ends_with('\n') {
&line[..line.len() - 1] &line[..line.len() - 1]
} else { } else {
@ -842,15 +874,14 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline /// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
pub async fn process_line( pub async fn process_line(
readline: Result<String, ReadlineError>, line: &str,
ctx: &mut Context, ctx: &mut EvaluationContext,
redirect_stdin: bool, redirect_stdin: bool,
cli_mode: bool, cli_mode: bool,
) -> LineResult { ) -> LineResult {
match &readline { if line.trim() == "" {
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()), LineResult::Success(line.to_string())
} else {
Ok(line) => {
let line = chomp_newline(line); let line = chomp_newline(line);
ctx.raw_input = line.to_string(); ctx.raw_input = line.to_string();
@ -1038,13 +1069,6 @@ pub async fn process_line(
Err(err) => LineResult::Error(line.to_string(), err), Err(err) => LineResult::Error(line.to_string(), err),
} }
} }
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::Break,
Err(err) => {
outln!("Error: {:?}", err);
LineResult::Break
}
}
} }
pub fn print_err(err: ShellError, source: &Text) { pub fn print_err(err: ShellError, source: &Text) {
@ -1069,7 +1093,7 @@ mod tests {
#[quickcheck] #[quickcheck]
fn quickcheck_parse(data: String) -> bool { fn quickcheck_parse(data: String) -> bool {
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) { if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
let context = crate::context::Context::basic().unwrap(); let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, context.registry()); let _ = nu_parser::classify_block(&lite_block, context.registry());
} }
true true

View File

@ -0,0 +1,64 @@
use crate::commands::Command;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::Signature;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct CommandRegistry {
registry: Arc<Mutex<IndexMap<String, Command>>>,
}
impl SignatureRegistry for CommandRegistry {
fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
fn get(&self, name: &str) -> Option<Signature> {
let registry = self.registry.lock();
registry.get(name).map(|command| command.signature())
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
impl CommandRegistry {
pub fn new() -> CommandRegistry {
CommandRegistry {
registry: Arc::new(Mutex::new(IndexMap::default())),
}
}
}
impl CommandRegistry {
pub fn get_command(&self, name: &str) -> Option<Command> {
let registry = self.registry.lock();
registry.get(name).cloned()
}
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
self.get_command(name).ok_or_else(|| {
ShellError::untagged_runtime_error(format!("Could not load command: {}", name))
})
}
pub fn has(&self, name: &str) -> bool {
let registry = self.registry.lock();
registry.contains_key(name)
}
pub fn insert(&mut self, name: impl Into<String>, command: Command) {
let mut registry = self.registry.lock();
registry.insert(name.into(), command);
}
pub fn names(&self) -> Vec<String> {
let registry = self.registry.lock();
registry.keys().cloned().collect()
}
}

View File

@ -18,7 +18,7 @@ pub(crate) mod cal;
pub(crate) mod cd; pub(crate) mod cd;
pub(crate) mod char_; pub(crate) mod char_;
pub(crate) mod classified; pub(crate) mod classified;
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard-cli")]
pub(crate) mod clip; pub(crate) mod clip;
pub(crate) mod command; pub(crate) mod command;
pub(crate) mod compact; pub(crate) mod compact;
@ -36,6 +36,7 @@ pub(crate) mod each;
pub(crate) mod echo; pub(crate) mod echo;
pub(crate) mod enter; pub(crate) mod enter;
pub(crate) mod every; pub(crate) mod every;
pub(crate) mod exec;
pub(crate) mod exit; pub(crate) mod exit;
pub(crate) mod first; pub(crate) mod first;
pub(crate) mod format; pub(crate) mod format;
@ -75,6 +76,7 @@ pub(crate) mod mkdir;
pub(crate) mod move_; pub(crate) mod move_;
pub(crate) mod next; pub(crate) mod next;
pub(crate) mod nth; pub(crate) mod nth;
pub(crate) mod nu;
pub(crate) mod open; pub(crate) mod open;
pub(crate) mod parse; pub(crate) mod parse;
pub(crate) mod path; pub(crate) mod path;
@ -114,7 +116,6 @@ pub(crate) mod to_tsv;
pub(crate) mod to_url; pub(crate) mod to_url;
pub(crate) mod to_xml; pub(crate) mod to_xml;
pub(crate) mod to_yaml; pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod uniq; pub(crate) mod uniq;
pub(crate) mod update; pub(crate) mod update;
pub(crate) mod url_; pub(crate) mod url_;
@ -154,9 +155,12 @@ pub(crate) use do_::Do;
pub(crate) use drop::Drop; pub(crate) use drop::Drop;
pub(crate) use du::Du; pub(crate) use du::Du;
pub(crate) use each::Each; pub(crate) use each::Each;
pub(crate) use each::EachGroup;
pub(crate) use each::EachWindow;
pub(crate) use echo::Echo; pub(crate) use echo::Echo;
pub(crate) use if_::If; pub(crate) use if_::If;
pub(crate) use is_empty::IsEmpty; pub(crate) use is_empty::IsEmpty;
pub(crate) use nu::NuPlugin;
pub(crate) use update::Update; pub(crate) use update::Update;
pub(crate) mod kill; pub(crate) mod kill;
pub(crate) use kill::Kill; pub(crate) use kill::Kill;
@ -165,6 +169,7 @@ pub(crate) use clear::Clear;
pub(crate) mod touch; pub(crate) mod touch;
pub(crate) use enter::Enter; pub(crate) use enter::Enter;
pub(crate) use every::Every; pub(crate) use every::Every;
pub(crate) use exec::Exec;
pub(crate) use exit::Exit; pub(crate) use exit::Exit;
pub(crate) use first::First; pub(crate) use first::First;
pub(crate) use format::Format; pub(crate) use format::Format;
@ -218,7 +223,7 @@ pub(crate) use prev::Previous;
pub(crate) use pwd::Pwd; pub(crate) use pwd::Pwd;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
pub(crate) use random::RandomUUID; pub(crate) use random::RandomUUID;
pub(crate) use random::{Random, RandomBool, RandomDice}; pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
pub(crate) use range::Range; pub(crate) use range::Range;
pub(crate) use reduce::Reduce; pub(crate) use reduce::Reduce;
pub(crate) use reject::Reject; pub(crate) use reject::Reject;
@ -255,7 +260,6 @@ pub(crate) use to_url::ToURL;
pub(crate) use to_xml::ToXML; pub(crate) use to_xml::ToXML;
pub(crate) use to_yaml::ToYAML; pub(crate) use to_yaml::ToYAML;
pub(crate) use touch::Touch; pub(crate) use touch::Touch;
pub(crate) use trim::Trim;
pub(crate) use uniq::Uniq; pub(crate) use uniq::Uniq;
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme}; pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
pub(crate) use version::Version; pub(crate) use version::Version;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_data::config; use nu_data::config;
use nu_errors::ShellError; use nu_errors::ShellError;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -37,7 +37,7 @@ pub trait ConfigExtensions: Debug + Send {
} }
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode { pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
let vars = config.vars.lock(); let vars = &config.vars;
if let Some(mode) = vars.get("pivot_mode") { if let Some(mode) = vars.get("pivot_mode") {
let mode = match mode.as_string() { let mode = match mode.as_string() {

View File

@ -1,18 +1,22 @@
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
#[cfg(feature = "rich-benchmark")]
use heim::cpu::time;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, hir::{Block, ClassifiedCommand, Commands, InternalCommand},
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
}; };
use std::convert::TryInto;
use chrono::prelude::*; use std::time::{Duration, Instant};
pub struct Benchmark; pub struct Benchmark;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct BenchmarkArgs { struct BenchmarkArgs {
block: Block, block: Block,
passthrough: Option<Block>,
} }
#[async_trait] #[async_trait]
@ -22,15 +26,22 @@ impl WholeStreamCommand for Benchmark {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("benchmark").required( Signature::build("benchmark")
.required(
"block", "block",
SyntaxShape::Block, SyntaxShape::Block,
"the block to run and benchmark", "the block to run and benchmark",
) )
.named(
"passthrough",
SyntaxShape::Block,
"Display the benchmark results and pass through the block's output",
Some('p'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }" "Runs a block and returns the time it took to execute it"
} }
async fn run( async fn run(
@ -40,6 +51,21 @@ impl WholeStreamCommand for Benchmark {
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
benchmark(args, registry).await benchmark(args, registry).await
} }
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Benchmarks a command within a block",
example: "benchmark { sleep 500ms }",
result: None,
},
Example {
description: "Benchmarks a command within a block and passes its output through",
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
result: Some(vec![UntaggedValue::int(45).into()]),
},
]
}
} }
async fn benchmark( async fn benchmark(
@ -48,11 +74,15 @@ async fn benchmark(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let mut context = Context::from_raw(&raw_args, &registry); let tag = raw_args.call_info.args.span;
let mut context = EvaluationContext::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block }, input) = raw_args.process(&registry).await?; let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(&registry).await?;
let start_time: chrono::DateTime<_> = Utc::now(); let start_time = Instant::now();
#[cfg(feature = "rich-benchmark")]
let start = time().await;
let result = run_block( let result = run_block(
&block, &block,
@ -63,16 +93,110 @@ async fn benchmark(
&scope.env, &scope.env,
) )
.await; .await;
let output = result?.into_vec().await;
let _ = result?.drain_vec().await; #[cfg(feature = "rich-benchmark")]
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time); let end = time().await;
let end_time = Instant::now();
context.clear_errors(); context.clear_errors();
let output = Ok(ReturnSuccess::Value(Value { // return basic runtime
value: UntaggedValue::Primitive(Primitive::from(run_duration)), #[cfg(not(feature = "rich-benchmark"))]
tag: Tag::from(block.span), {
})); let mut indexmap = IndexMap::with_capacity(1);
Ok(OutputStream::from(vec![output])) let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time);
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
}
// return advanced stats
#[cfg(feature = "rich-benchmark")]
if let (Ok(start), Ok(end)) = (start, end) {
let mut indexmap = IndexMap::with_capacity(4);
let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time);
let user_time = into_big_int(end.user() - start.user());
indexmap.insert("user time".to_string(), user_time);
let system_time = into_big_int(end.system() - start.system());
indexmap.insert("system time".to_string(), system_time);
let idle_time = into_big_int(end.idle() - start.idle());
indexmap.insert("idle time".to_string(), idle_time);
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
} else {
Err(ShellError::untagged_runtime_error(
"Could not retreive CPU time",
))
}
}
async fn benchmark_output<T, Output>(
indexmap: IndexMap<String, BigInt>,
block_output: Output,
passthrough: Option<Block>,
tag: T,
context: &mut EvaluationContext,
scope: &Scope,
) -> Result<OutputStream, ShellError>
where
T: Into<Tag> + Copy,
Output: Into<OutputStream>,
{
let value = UntaggedValue::Row(Dictionary::from(
indexmap
.into_iter()
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
.collect::<IndexMap<String, Value>>(),
))
.into_value(tag);
if let Some(time_block) = passthrough {
let benchmark_output = InputStream::one(value);
// add autoview for an empty block
let time_block = add_implicit_autoview(time_block);
let _ = run_block(
&time_block,
context,
benchmark_output,
&scope.it,
&scope.vars,
&scope.env,
)
.await?;
context.clear_errors();
Ok(block_output.into())
} else {
let benchmark_output = OutputStream::one(value);
Ok(benchmark_output)
}
}
fn add_implicit_autoview(mut block: Block) -> Block {
if block.block.is_empty() {
block.push({
let mut commands = Commands::new(block.span);
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
"autoview".to_string(),
block.span,
block.span,
)));
commands
});
}
block
}
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
time.try_into()
.unwrap_or_else(|_| Duration::new(0, 0))
.as_nanos()
.into()
} }

View File

@ -94,6 +94,35 @@ fn str_to_character(s: &str) -> Option<String> {
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡ "high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~ "tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // # "hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
// Weather symbols
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
"bel" => Some('\x07'.to_string()), // Terminal Bell
"backspace" => Some('\x08'.to_string()), // Backspace
// Ansi Erase Sequences
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
_ => None, _ => None,
} }
} }

View File

@ -1,6 +1,6 @@
use crate::commands::classified::expr::run_expression_block; use crate::commands::classified::expr::run_expression_block;
use crate::commands::classified::internal::run_internal_command; use crate::commands::classified::internal::run_internal_command;
use crate::context::Context; use crate::evaluation_context::EvaluationContext;
use crate::prelude::*; use crate::prelude::*;
use crate::stream::InputStream; use crate::stream::InputStream;
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
@ -11,7 +11,7 @@ use std::sync::atomic::Ordering;
pub(crate) async fn run_block( pub(crate) async fn run_block(
block: &Block, block: &Block,
ctx: &mut Context, ctx: &mut EvaluationContext,
mut input: InputStream, mut input: InputStream,
it: &Value, it: &Value,
vars: &IndexMap<String, Value>, vars: &IndexMap<String, Value>,
@ -64,7 +64,7 @@ pub(crate) async fn run_block(
async fn run_pipeline( async fn run_pipeline(
commands: &Commands, commands: &Commands,
ctx: &mut Context, ctx: &mut EvaluationContext,
mut input: InputStream, mut input: InputStream,
it: &Value, it: &Value,
vars: &IndexMap<String, Value>, vars: &IndexMap<String, Value>,

View File

@ -10,7 +10,7 @@ use nu_protocol::Value;
pub(crate) async fn run_expression_block( pub(crate) async fn run_expression_block(
expr: SpannedExpression, expr: SpannedExpression,
context: &mut Context, context: &mut EvaluationContext,
it: &Value, it: &Value,
vars: &IndexMap<String, Value>, vars: &IndexMap<String, Value>,
env: &IndexMap<String, String>, env: &IndexMap<String, String>,

View File

@ -19,7 +19,7 @@ use nu_source::Tag;
pub(crate) async fn run_external_command( pub(crate) async fn run_external_command(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut EvaluationContext,
input: InputStream, input: InputStream,
scope: &Scope, scope: &Scope,
external_redirection: ExternalRedirection, external_redirection: ExternalRedirection,
@ -39,7 +39,7 @@ pub(crate) async fn run_external_command(
async fn run_with_stdin( async fn run_with_stdin(
command: ExternalCommand, command: ExternalCommand,
context: &mut Context, context: &mut EvaluationContext,
input: InputStream, input: InputStream,
scope: &Scope, scope: &Scope,
external_redirection: ExternalRedirection, external_redirection: ExternalRedirection,
@ -543,7 +543,7 @@ mod tests {
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes, add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
}; };
#[cfg(feature = "which")] #[cfg(feature = "which")]
use super::{run_external_command, Context, InputStream}; use super::{run_external_command, EvaluationContext, InputStream};
#[cfg(feature = "which")] #[cfg(feature = "which")]
use futures::executor::block_on; use futures::executor::block_on;
@ -573,7 +573,8 @@ mod tests {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build(); let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
let input = InputStream::empty(); let input = InputStream::empty();
let mut ctx = Context::basic().expect("There was a problem creating a basic context."); let mut ctx =
EvaluationContext::basic().expect("There was a problem creating a basic context.");
assert!(run_external_command( assert!(run_external_command(
cmd, cmd,
@ -591,7 +592,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> { // async fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build(); // let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = Context::basic().expect("There was a problem creating a basic context."); // let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false) // let stream = run_external_command(cmd, &mut ctx, None, false)
// .await? // .await?
// .expect("There was a problem running the external command."); // .expect("There was a problem running the external command.");

View File

@ -9,7 +9,7 @@ use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue,
pub(crate) async fn run_internal_command( pub(crate) async fn run_internal_command(
command: InternalCommand, command: InternalCommand,
context: &mut Context, context: &mut EvaluationContext,
input: InputStream, input: InputStream,
it: &Value, it: &Value,
vars: &IndexMap<String, Value>, vars: &IndexMap<String, Value>,
@ -200,6 +200,28 @@ pub(crate) async fn run_internal_command(
)]); )]);
InputStream::from_stream(futures::stream::iter(vec![])) InputStream::from_stream(futures::stream::iter(vec![]))
} }
CommandAction::AddPlugins(path) => {
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
Ok(plugins) => {
context.add_commands(
plugins
.into_iter()
.filter(|p| {
!context.is_command_registered(p.name())
})
.collect(),
);
InputStream::from_stream(futures::stream::iter(vec![]))
}
Err(reason) => {
context.error(reason.clone());
InputStream::one(
UntaggedValue::Error(reason).into_untagged_value(),
)
}
}
}
CommandAction::PreviousShell => { CommandAction::PreviousShell => {
context.shell_manager.prev(); context.shell_manager.prev();
InputStream::from_stream(futures::stream::iter(vec![])) InputStream::from_stream(futures::stream::iter(vec![]))

View File

@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"clears the terminal" "Clears the terminal"
} }
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> { async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use nu_errors::ShellError; use nu_errors::ShellError;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::help::get_help; use crate::commands::help::get_help;
use crate::context::CommandRegistry;
use crate::deserializer::ConfigDeserializer; use crate::deserializer::ConfigDeserializer;
use crate::evaluate::evaluate_args::evaluate_args; use crate::evaluate::evaluate_args::evaluate_args;
use crate::prelude::*; use crate::prelude::*;
@ -303,6 +303,11 @@ pub trait WholeStreamCommand: Send + Sync {
false false
} }
// Commands that are not meant to be run by users
fn is_internal(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
Vec::new() Vec::new()
} }
@ -367,6 +372,10 @@ impl Command {
self.0.is_binary() self.0.is_binary()
} }
pub fn is_internal(&self) -> bool {
self.0.is_internal()
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand { pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0 &*self.0
} }

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::future; use futures::future;
use futures::stream::StreamExt; use futures::stream::StreamExt;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use nu_errors::ShellError; use nu_errors::ShellError;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};

View File

@ -16,7 +16,7 @@ impl WholeStreamCommand for Command {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Work with dates." "Apply date function"
} }
async fn run( async fn run(

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -65,7 +65,7 @@ async fn do_(
let registry = registry.clone(); let registry = registry.clone();
let external_redirection = raw_args.call_info.args.external_redirection; let external_redirection = raw_args.call_info.args.external_redirection;
let mut context = Context::from_raw(&raw_args, &registry); let mut context = EvaluationContext::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let ( let (
DoArgs { DoArgs {

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};

View File

@ -1,6 +1,6 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::stream::once; use futures::stream::once;
@ -84,7 +84,7 @@ pub async fn process_row(
block: Arc<Block>, block: Arc<Block>,
scope: Arc<Scope>, scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>, head: Arc<Box<SpannedExpression>>,
mut context: Arc<Context>, mut context: Arc<EvaluationContext>,
input: Value, input: Value,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let input_clone = input.clone(); let input_clone = input.clone();
@ -120,7 +120,7 @@ async fn each(
let registry = registry.clone(); let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone()); let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone()); let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?; let (each_args, input): (EachArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block); let block = Arc::new(each_args.block);

View File

@ -0,0 +1,133 @@
use crate::commands::each::process_row;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
UntaggedValue, Value,
};
use nu_source::Tagged;
use serde::Deserialize;
pub struct EachGroup;
#[derive(Deserialize)]
pub struct EachGroupArgs {
group_size: Tagged<usize>,
block: Block,
//numbered: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for EachGroup {
fn name(&self) -> &str {
"each group"
}
fn signature(&self) -> Signature {
Signature::build("each group")
.required("group_size", SyntaxShape::Int, "the size of each group")
.required(
"block",
SyntaxShape::Block,
"the block to run on each group",
)
}
fn usage(&self) -> &str {
"Runs a block on groups of `group_size` rows of a table at a time."
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Echo the sum of each pair",
example: "echo [1 2 3 4] | each group 2 { echo $it | math sum }",
result: None,
}]
}
async fn run(
&self,
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, input): (EachGroupArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
Ok(input
.chunks(each_args.group_size.item)
.then(move |input| {
run_block_on_vec(
input,
block.clone(),
scope.clone(),
head.clone(),
context.clone(),
)
})
.flatten()
.to_output_stream())
}
}
pub(crate) fn run_block_on_vec(
input: Vec<Value>,
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
context: Arc<EvaluationContext>,
) -> impl Future<Output = OutputStream> {
let value = Value {
value: UntaggedValue::Table(input),
tag: Tag::unknown(),
};
async {
match process_row(block, scope, head, context, value).await {
Ok(s) => {
// We need to handle this differently depending on whether process_row
// returned just 1 value or if it returned multiple as a stream.
let vec = s.collect::<Vec<_>>().await;
// If it returned just one value, just take that value
if vec.len() == 1 {
return OutputStream::one(vec.into_iter().next().expect(
"This should be impossible, we just checked that vec.len() == 1.",
));
}
// If it returned multiple values, we need to put them into a table and
// return that.
let result = vec.into_iter().collect::<Result<Vec<ReturnSuccess>, _>>();
let result_table = match result {
Ok(t) => t,
Err(e) => return OutputStream::one(Err(e)),
};
let table = result_table
.into_iter()
.filter_map(|x| x.raw_value())
.collect();
OutputStream::one(Ok(ReturnSuccess::Value(UntaggedValue::Table(table).into())))
}
Err(e) => OutputStream::one(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use super::EachGroup;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(EachGroup {})
}
}

View File

@ -0,0 +1,9 @@
pub mod command;
pub mod group;
pub mod window;
pub(crate) use command::make_indexed_item;
pub use command::process_row;
pub use command::Each;
pub use group::EachGroup;
pub use window::EachWindow;

View File

@ -0,0 +1,113 @@
use crate::commands::each::group::run_block_on_vec;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
//use itertools::Itertools;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, Primitive, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use serde::Deserialize;
pub struct EachWindow;
#[derive(Deserialize)]
pub struct EachWindowArgs {
window_size: Tagged<usize>,
block: Block,
stride: Option<Tagged<usize>>,
}
#[async_trait]
impl WholeStreamCommand for EachWindow {
fn name(&self) -> &str {
"each window"
}
fn signature(&self) -> Signature {
Signature::build("each window")
.required("window_size", SyntaxShape::Int, "the size of each window")
.named(
"stride",
SyntaxShape::Int,
"the number of rows to slide over between windows",
Some('s'),
)
.required(
"block",
SyntaxShape::Block,
"the block to run on each group",
)
}
fn usage(&self) -> &str {
"Runs a block on sliding windows of `window_size` rows of a table at a time."
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Echo the sum of each window",
example: "echo [1 2 3 4] | each window 2 { echo $it | math sum }",
result: None,
}]
}
async fn run(
&self,
raw_args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let head = Arc::new(raw_args.call_info.args.head.clone());
let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(each_args.block);
let mut window: Vec<_> = input
.by_ref()
.take(*each_args.window_size - 1)
.collect::<Vec<_>>()
.await;
// `window` must start with dummy values, which will be removed on the first iteration
let stride = each_args.stride.map(|x| *x).unwrap_or(1);
window.insert(0, UntaggedValue::Primitive(Primitive::Nothing).into());
Ok(input
.enumerate()
.then(move |(i, input)| {
// This would probably be more efficient if `last` was a VecDeque
// But we can't have that because it needs to be put into a Table
window.remove(0);
window.push(input);
let block = block.clone();
let scope = scope.clone();
let head = head.clone();
let context = context.clone();
let local_window = window.clone();
async move {
if i % stride == 0 {
Some(run_block_on_vec(local_window, block, scope, head, context).await)
} else {
None
}
}
})
.filter_map(|x| async { x })
.flatten()
.to_output_stream())
}
}
#[cfg(test)]
mod tests {
use super::EachWindow;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(EachWindow {})
}
}

View File

@ -81,17 +81,20 @@ struct RangeIterator {
end: Primitive, end: Primitive,
tag: Tag, tag: Tag,
is_end_inclusive: bool, is_end_inclusive: bool,
is_done: bool,
} }
impl RangeIterator { impl RangeIterator {
pub fn new(range: Range, tag: Tag) -> RangeIterator { pub fn new(range: Range, tag: Tag) -> RangeIterator {
let start = match range.from.0.item {
Primitive::Nothing => Primitive::Int(0.into()),
x => x,
};
RangeIterator { RangeIterator {
curr: range.from.0.item, curr: start,
end: range.to.0.item, end: range.to.0.item,
tag, tag,
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive), is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
is_done: false,
} }
} }
} }
@ -99,14 +102,40 @@ impl RangeIterator {
impl Iterator for RangeIterator { impl Iterator for RangeIterator {
type Item = Result<ReturnSuccess, ShellError>; type Item = Result<ReturnSuccess, ShellError>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.curr != self.end { let ordering = if self.end == Primitive::Nothing {
Ordering::Less
} else {
let result =
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
ShellError::labeled_error(
"Cannot create range",
"unsupported range",
self.tag.span,
)
});
if let Err(result) = result {
return Some(Err(result));
}
let result = result
.expect("Internal error: the error case was already protected, but that failed");
result.compare()
};
use std::cmp::Ordering;
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()); let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
self.curr = match nu_data::value::compute_values( let next_value = nu_data::value::compute_values(
Operator::Plus, Operator::Plus,
&UntaggedValue::Primitive(self.curr.clone()), &UntaggedValue::Primitive(self.curr.clone()),
&UntaggedValue::int(1), &UntaggedValue::int(1),
) { );
self.curr = match next_value {
Ok(result) => match result { Ok(result) => match result {
UntaggedValue::Primitive(p) => p, UntaggedValue::Primitive(p) => p,
_ => { _ => {
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
} }
}; };
Some(ReturnSuccess::value(output)) Some(ReturnSuccess::value(output))
} else if self.is_end_inclusive && !self.is_done {
self.is_done = true;
Some(ReturnSuccess::value(
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
))
} else { } else {
// TODO: add inclusive/exclusive ranges // TODO: add inclusive/exclusive ranges
None None

View File

@ -1,6 +1,6 @@
use crate::command_registry::CommandRegistry;
use crate::commands::UnevaluatedCallInfo; use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir::ExternalRedirection; use nu_protocol::hir::ExternalRedirection;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};

View File

@ -0,0 +1,87 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct Exec;
#[derive(Deserialize)]
pub struct ExecArgs {
pub command: Tagged<PathBuf>,
pub rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Exec {
fn name(&self) -> &str {
"exec"
}
fn signature(&self) -> Signature {
Signature::build("exec")
.required("command", SyntaxShape::Path, "the command to execute")
.rest(SyntaxShape::Pattern, "any additional arguments for command")
}
fn usage(&self) -> &str {
"Execute command"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
exec(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Execute 'ps aux'",
example: "exec ps aux",
result: None,
},
Example {
description: "Execute 'nautilus'",
example: "exec nautilus",
result: None,
},
]
}
}
#[cfg(unix)]
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
use std::os::unix::process::CommandExt;
use std::process::Command;
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (args, _): (ExecArgs, _) = args.process(&registry).await?;
let mut command = Command::new(args.command.item);
for tagged_arg in args.rest {
command.arg(tagged_arg.item);
}
let err = command.exec(); // this replaces our process, should not return
Err(ShellError::labeled_error(
"Error on exec",
format!("{}", err),
&name,
))
}
#[cfg(not(unix))]
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"Error on exec",
"exec is not supported on your platform",
&args.call_info.name_tag,
))
}

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::command::WholeStreamCommand; use crate::commands::command::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature}; use nu_protocol::{CommandAction, ReturnSuccess, Signature};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr; use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;

View File

@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
.delimiter(separator as u8) .delimiter(separator as u8)
.from_reader(s.as_bytes()); .from_reader(s.as_bytes());
let tag = tag.into(); let tag = tag.into();
let span = tag.span;
let headers = if headerless { let headers = if headerless {
(1..=reader.headers()?.len()) (1..=reader.headers()?.len())
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
if let Ok(i) = value.parse::<i64>() { if let Ok(i) = value.parse::<i64>() {
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag)) tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
} else if let Ok(f) = value.parse::<f64>() { } else if let Ok(f) = value.parse::<f64>() {
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag)) tagged_row.insert_value(
header,
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
)
} else { } else {
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag)) tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
} }

View File

@ -39,11 +39,12 @@ impl WholeStreamCommand for FromJSON {
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value { fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into(); let tag = tag.into();
let span = tag.span;
match v { match v {
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag), serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag), serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag), serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag), serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag), serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::String(s) => { serde_hjson::Value::String(s) => {

View File

@ -45,6 +45,7 @@ async fn from_ods(
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone(); let registry = registry.clone();
let ( let (
@ -73,7 +74,7 @@ async fn from_ods(
let value = match cell { let value = match cell {
DataType::Empty => UntaggedValue::nothing(), DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s), DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f), DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
DataType::Int(i) => UntaggedValue::int(*i), DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b), DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(), _ => UntaggedValue::nothing(),

View File

@ -30,11 +30,12 @@ impl WholeStreamCommand for FromTOML {
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value { pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into(); let tag = tag.into();
let span = tag.span;
match v { match v {
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag), toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag), toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
toml::Value::Float(n) => UntaggedValue::decimal(*n).into_value(tag), toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
toml::Value::String(s) => { toml::Value::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag) UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
} }

View File

@ -45,6 +45,7 @@ async fn from_xlsx(
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone(); let registry = registry.clone();
let ( let (
FromXLSXArgs { FromXLSXArgs {
@ -73,7 +74,7 @@ async fn from_xlsx(
let value = match cell { let value = match cell {
DataType::Empty => UntaggedValue::nothing(), DataType::Empty => UntaggedValue::nothing(),
DataType::String(s) => UntaggedValue::string(s), DataType::String(s) => UntaggedValue::string(s),
DataType::Float(f) => UntaggedValue::decimal(*f), DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
DataType::Int(i) => UntaggedValue::int(*i), DataType::Int(i) => UntaggedValue::int(*i),
DataType::Bool(b) => UntaggedValue::boolean(*b), DataType::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(), _ => UntaggedValue::nothing(),

View File

@ -58,6 +58,7 @@ fn convert_yaml_value_to_nu_value(
tag: impl Into<Tag>, tag: impl Into<Tag>,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let tag = tag.into(); let tag = tag.into();
let span = tag.span;
let err_not_compatible_number = ShellError::labeled_error( let err_not_compatible_number = ShellError::labeled_error(
"Expected a compatible number", "Expected a compatible number",
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
serde_yaml::Value::Number(n) if n.is_i64() => { serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag) UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
} }
serde_yaml::Value::Number(n) if n.is_f64() => { serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?) n.as_f64().ok_or_else(|| err_not_compatible_number)?,
.into_value(tag) span,
} )
.into_value(tag),
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag), serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => { serde_yaml::Value::Sequence(a) => {
let result: Result<Vec<Value>, ShellError> = a let result: Result<Vec<Value>, ShellError> = a

View File

@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
use log::trace; use log::trace;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape, did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
UnspannedPathMember, UntaggedValue, Value, SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
}; };
use nu_source::HasFallibleSpan; use nu_source::HasFallibleSpan;
use nu_value_ext::get_data_by_column_path; use nu_value_ext::get_data_by_column_path;
@ -58,17 +58,95 @@ impl WholeStreamCommand for Get {
} }
} }
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> { pub async fn get(
let fields = path.clone(); args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (GetArgs { rest: column_paths }, mut input) = args.process(&registry).await?;
if column_paths.is_empty() {
let vec = input.drain_vec().await;
get_data_by_column_path( let descs = nu_protocol::merge_descriptors(&vec);
obj,
path, Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
Box::new(move |(obj_source, column_path_tried, error)| { } else {
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown); trace!("get {:?}", column_paths);
let output_stream = input
.map(move |item| {
let output = column_paths
.iter()
.map(move |path| get_output(&item, path))
.flatten()
.collect::<Vec<_>>();
futures::stream::iter(output)
})
.flatten()
.to_output_stream();
Ok(output_stream)
}
}
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
match get_column_path(path, item) {
Ok(Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
}) => vec![],
Ok(Value {
value: UntaggedValue::Table(rows),
..
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
Ok(other) => vec![ReturnSuccess::value(other)],
Err(reason) => vec![ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)],
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
match &obj_source.value { match &obj_source.value {
UntaggedValue::Table(rows) => match column_path_tried { UntaggedValue::Table(rows) => {
return get_column_path_from_table_error(
rows,
column_path_tried,
&path_members_span,
);
}
UntaggedValue::Row(columns) => {
if let Some(error) = get_column_from_row_error(
columns,
column_path_tried,
&path_members_span,
obj_source,
) {
return error;
}
}
_ => {}
}
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
column_path_tried.span.since(path_members_span),
)
} else {
error
}
})
}
pub fn get_column_path_from_table_error(
rows: &[Value],
column_path_tried: &PathMember,
path_members_span: &Span,
) -> ShellError {
match column_path_tried {
PathMember { PathMember {
unspanned: UnspannedPathMember::String(column), unspanned: UnspannedPathMember::String(column),
.. ..
@ -93,15 +171,15 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
} }
if names.is_empty() { if names.is_empty() {
return ShellError::labeled_error_with_secondary( ShellError::labeled_error_with_secondary(
"Unknown column", "Unknown column",
primary_label, primary_label,
column_path_tried.span, column_path_tried.span,
"Appears to contain rows. Try indexing instead.", "Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
); )
} else { } else {
return ShellError::labeled_error_with_secondary( ShellError::labeled_error_with_secondary(
"Unknown column", "Unknown column",
primary_label, primary_label,
column_path_tried.span, column_path_tried.span,
@ -115,8 +193,8 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
names.join(", ") names.join(", ")
), ),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
); )
}; }
} }
PathMember { PathMember {
unspanned: UnspannedPathMember::Int(idx), unspanned: UnspannedPathMember::Int(idx),
@ -130,16 +208,24 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
format!("The table only has {} rows (0 to {})", total, total - 1) format!("The table only has {} rows (0 to {})", total, total - 1)
}; };
return ShellError::labeled_error_with_secondary( ShellError::labeled_error_with_secondary(
"Row not found", "Row not found",
format!("There isn't a row indexed at {}", idx), format!("There isn't a row indexed at {}", idx),
column_path_tried.span, column_path_tried.span,
secondary_label, secondary_label,
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
); )
} }
}, }
UntaggedValue::Row(columns) => match column_path_tried { }
pub fn get_column_from_row_error(
columns: &Dictionary,
column_path_tried: &PathMember,
path_members_span: &Span,
obj_source: &Value,
) -> Option<ShellError> {
match column_path_tried {
PathMember { PathMember {
unspanned: UnspannedPathMember::String(column), unspanned: UnspannedPathMember::String(column),
.. ..
@ -147,7 +233,7 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
let primary_label = format!("There isn't a column named '{}'", &column); let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) { if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error_with_secondary( Some(ShellError::labeled_error_with_secondary(
"Unknown column", "Unknown column",
primary_label, primary_label,
column_path_tried.span, column_path_tried.span,
@ -157,14 +243,15 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
&obj_source.data_descriptors().join(", ") &obj_source.data_descriptors().join(", ")
), ),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
); ))
} else {
None
} }
} }
PathMember { PathMember {
unspanned: UnspannedPathMember::Int(idx), unspanned: UnspannedPathMember::Int(idx),
.. ..
} => { } => Some(ShellError::labeled_error_with_secondary(
return ShellError::labeled_error_with_secondary(
"No rows available", "No rows available",
format!("A row at '{}' can't be indexed.", &idx), format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span, column_path_tried.span,
@ -173,83 +260,10 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
columns.keys().join(", ") columns.keys().join(", ")
), ),
column_path_tried.span.since(path_members_span), column_path_tried.span.since(path_members_span),
)
}
},
_ => {}
}
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
column_path_tried.span.since(path_members_span),
);
}
error
}),
)
}
pub async fn get(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (GetArgs { rest: mut fields }, mut input) = args.process(&registry).await?;
if fields.is_empty() {
let vec = input.drain_vec().await;
let descs = nu_protocol::merge_descriptors(&vec);
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
Ok(input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &fields]
.into_iter()
.flatten()
.collect::<Vec<&ColumnPath>>();
let mut output = vec![];
for path in column_paths {
let res = get_column_path(&path, &item);
match res {
Ok(got) => match got {
Value {
value: UntaggedValue::Table(rows),
..
} => {
for item in rows {
output.push(ReturnSuccess::value(item.clone()));
}
}
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
..
} => {}
other => output.push(ReturnSuccess::value(other.clone())),
},
Err(reason) => output.push(ReturnSuccess::value(
UntaggedValue::Error(reason).into_untagged_value(),
)), )),
} }
} }
futures::stream::iter(output)
})
.flatten()
.to_output_stream())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Get; use super::Get;

View File

@ -28,7 +28,7 @@ impl WholeStreamCommand for GroupBy {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"create a new table grouped." "Create a new table grouped."
} }
async fn run( async fn run(
@ -96,7 +96,7 @@ pub async fn group_by(
let registry = registry.clone(); let registry = registry.clone();
let head = Arc::new(args.call_info.args.head.clone()); let head = Arc::new(args.call_info.args.head.clone());
let scope = Arc::new(args.call_info.scope.clone()); let scope = Arc::new(args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&args, &registry));
let (GroupByArgs { grouper }, input) = args.process(&registry).await?; let (GroupByArgs { grouper }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await; let values: Vec<Value> = input.collect().await;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use indexmap::IndexMap; use indexmap::IndexMap;

View File

@ -63,54 +63,114 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let mut sorted_names = registry.names(); let mut sorted_names = registry.names();
sorted_names.sort(); sorted_names.sort();
Ok( let (mut subcommand_names, command_names) = sorted_names
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| { .into_iter()
// If it's a subcommand, don't list it during the commands list // Internal only commands shouldn't be displayed
if cmd.contains(' ') { .filter(|cmd_name| {
return None; registry
} .get_command(&cmd_name)
let mut short_desc = TaggedDictBuilder::new(name.clone()); .filter(|command| !command.is_internal())
.is_some()
})
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
fn process_name(
dict: &mut TaggedDictBuilder,
cmd_name: &str,
registry: CommandRegistry,
rest: Vec<Tagged<String>>,
name: Tag,
) -> Result<(), ShellError> {
let document_tag = rest[0].tag.clone(); let document_tag = rest[0].tag.clone();
let value = command_dict( let value = command_dict(
match registry.get_command(&cmd).ok_or_else(|| { registry.get_command(&cmd_name).ok_or_else(|| {
ShellError::labeled_error( ShellError::labeled_error(
format!("Could not load {}", cmd), format!("Could not load {}", cmd_name),
"could not load command", "could not load command",
document_tag, document_tag,
) )
}) { })?,
Ok(ok) => ok, name,
Err(err) => return Some(Err(err)),
},
name.clone(),
); );
short_desc.insert_untagged("name", cmd); dict.insert_untagged("name", cmd_name);
short_desc.insert_untagged( dict.insert_untagged(
"description", "description",
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else( get_data_by_key(&value, "usage".spanned_unknown())
|| { .ok_or_else(|| {
ShellError::labeled_error( ShellError::labeled_error(
"Expected a usage key", "Expected a usage key",
"expected a 'usage' key", "expected a 'usage' key",
&value.tag, &value.tag,
) )
}, })?
) { .as_string()?,
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
); );
Some(ReturnSuccess::value(short_desc.into_value())) Ok(())
})) }
.to_output_stream(),
fn make_subcommands_table(
subcommand_names: &mut Vec<String>,
cmd_name: &str,
registry: CommandRegistry,
rest: Vec<Tagged<String>>,
name: Tag,
) -> Result<Value, ShellError> {
let (matching, not_matching) =
subcommand_names.drain(..).partition(|subcommand_name| {
subcommand_name.starts_with(&format!("{} ", cmd_name))
});
*subcommand_names = not_matching;
Ok(if !matching.is_empty() {
UntaggedValue::table(
&(matching
.into_iter()
.map(|cmd_name: String| -> Result<_, ShellError> {
let mut short_desc = TaggedDictBuilder::new(name.clone());
process_name(
&mut short_desc,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?;
Ok(short_desc.into_value())
})
.collect::<Result<Vec<_>, _>>()?[..]),
) )
.into_value(name)
} else {
UntaggedValue::nothing().into_value(name)
})
}
let iterator =
command_names
.into_iter()
.map(move |cmd_name| -> Result<_, ShellError> {
let mut short_desc = TaggedDictBuilder::new(name.clone());
process_name(
&mut short_desc,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?;
short_desc.insert_value(
"subcommands",
make_subcommands_table(
&mut subcommand_names,
&cmd_name,
registry.clone(),
rest.clone(),
name.clone(),
)?,
);
ReturnSuccess::value(short_desc.into_value())
});
Ok(futures::stream::iter(iterator).to_output_stream())
} else if rest[0].item == "generate_docs" { } else if rest[0].item == "generate_docs" {
Ok(OutputStream::one(ReturnSuccess::value(generate_docs( Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
&registry, &registry,

View File

@ -178,11 +178,7 @@ fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, Shell
Box::new(move |_: usize, value: &Value| { Box::new(move |_: usize, value: &Value| {
let path = by.clone(); let path = by.clone();
let eval = nu_value_ext::get_data_by_column_path( let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
value,
&path,
Box::new(move |(_, _, error)| error),
);
match eval { match eval {
Ok(with_value) => Ok(with_value), Ok(with_value) => Ok(with_value),

View File

@ -1,6 +1,6 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use nu_data::config::NuConfig; use nu_data::config::{Conf, NuConfig};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File; use std::fs::File;
@ -9,9 +9,7 @@ use std::path::PathBuf;
const DEFAULT_LOCATION: &str = "history.txt"; const DEFAULT_LOCATION: &str = "history.txt";
pub fn history_path(config: &NuConfig) -> PathBuf { pub fn history_path(config: &dyn Conf) -> PathBuf {
let vars = config.vars.lock();
let default_path = nu_data::config::user_data() let default_path = nu_data::config::user_data()
.map(|mut p| { .map(|mut p| {
p.push(DEFAULT_LOCATION); p.push(DEFAULT_LOCATION);
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
}) })
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION)); .unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
vars.get("history-path") config
.var("history-path")
.map_or(default_path.clone(), |custom_path| { .map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() { match custom_path.as_string() {
Ok(path) => PathBuf::from(path), Ok(path) => PathBuf::from(path),
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
} }
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let config = NuConfig::new(); let config: Box<dyn Conf> = Box::new(NuConfig::new());
let tag = args.call_info.name_tag; let tag = args.call_info.name_tag;
let path = history_path(&config); let path = history_path(&config);
let file = File::open(path); let file = File::open(path);

View File

@ -1,6 +1,6 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr; use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
@ -74,7 +74,7 @@ async fn if_command(
let registry = Arc::new(registry.clone()); let registry = Arc::new(registry.clone());
let scope = Arc::new(raw_args.call_info.scope.clone()); let scope = Arc::new(raw_args.call_info.scope.clone());
let tag = raw_args.call_info.name_tag.clone(); let tag = raw_args.call_info.name_tag.clone();
let context = Arc::new(Context::from_raw(&raw_args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let ( let (
IfArgs { IfArgs {

View File

@ -1,6 +1,6 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
@ -48,7 +48,7 @@ impl WholeStreamCommand for Insert {
async fn process_row( async fn process_row(
scope: Arc<Scope>, scope: Arc<Scope>,
mut context: Arc<Context>, mut context: Arc<EvaluationContext>,
input: Value, input: Value,
mut value: Arc<Value>, mut value: Arc<Value>,
field: Arc<ColumnPath>, field: Arc<ColumnPath>,
@ -136,7 +136,7 @@ async fn insert(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let scope = Arc::new(raw_args.call_info.scope.clone()); let scope = Arc::new(raw_args.call_info.scope.clone());
let context = Arc::new(Context::from_raw(&raw_args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (InsertArgs { column, value }, input) = raw_args.process(&registry).await?; let (InsertArgs { column, value }, input) = raw_args.process(&registry).await?;
let value = Arc::new(value); let value = Arc::new(value);
let column = Arc::new(column); let column = Arc::new(column);

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
@ -50,9 +50,16 @@ async fn is_empty(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone();
let registry = registry.clone(); let registry = registry.clone();
let (IsEmptyArgs { rest }, input) = args.process(&registry).await?; let (IsEmptyArgs { rest }, input) = args.process(&registry).await?;
if input.is_empty() {
return Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::boolean(true).into_value(name_tag),
)));
}
Ok(input Ok(input
.map(move |value| { .map(move |value| {
let value_tag = value.tag(); let value_tag = value.tag();
@ -72,7 +79,6 @@ async fn is_empty(
(_, _) => IsEmptyFor::Value, (_, _) => IsEmptyFor::Value,
} }
} else { } else {
// let no_args = vec![];
let mut arguments = rest.iter().rev(); let mut arguments = rest.iter().rev();
let replacement_if_true = match arguments.next() { let replacement_if_true = match arguments.next() {
Some(arg) => arg.clone(), Some(arg) => arg.clone(),

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -53,7 +53,11 @@ impl WholeStreamCommand for SubCommand {
vec![Example { vec![Example {
description: "Get the average of a list of numbers", description: "Get the average of a list of numbers",
example: "echo [-50 100.0 25] | math avg", example: "echo [-50 100.0 25] | math avg",
result: Some(vec![UntaggedValue::decimal(25).into()]), result: Some(vec![UntaggedValue::decimal_from_float(
25.0,
Span::unknown(),
)
.into()]),
}] }]
} }
} }
@ -137,6 +141,28 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
)), )),
} }
} }
Value {
value: UntaggedValue::Primitive(Primitive::Duration(duration)),
..
} => {
let left = UntaggedValue::from(Primitive::Duration(duration));
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Duration(result))) => {
Ok(UntaggedValue::duration(result).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate average of non-integer or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value { Value {
value: UntaggedValue::Primitive(other), value: UntaggedValue::Primitive(other),
.. ..

View File

@ -39,7 +39,7 @@ mod tests {
sum::summation, utils::calculate, utils::MathFunction, variance::variance, sum::summation, utils::calculate, utils::MathFunction, variance::variance,
}; };
use nu_plugin::row; use nu_plugin::row;
use nu_plugin::test_helpers::value::{decimal, int, table}; use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
use nu_protocol::Value; use nu_protocol::Value;
use std::str::FromStr; use std::str::FromStr;
@ -98,17 +98,17 @@ mod tests {
}, },
TestCase { TestCase {
description: "Mixed Values", description: "Mixed Values",
values: vec![int(10), decimal(26.5), decimal(26.5)], values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(21)), Ok(decimal(21)),
Ok(int(10)), Ok(int(10)),
Ok(decimal(26.5)), Ok(decimal_from_float(26.5)),
Ok(decimal(26.5)), Ok(decimal_from_float(26.5)),
Ok(table(&[decimal(26.5)])), Ok(table(&[decimal_from_float(26.5)])),
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))), Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
Ok(decimal(63)), Ok(decimal(63)),
Ok(decimal(60.5)), Ok(decimal_from_float(60.5)),
], ],
}, },
TestCase { TestCase {
@ -128,14 +128,14 @@ mod tests {
}, },
TestCase { TestCase {
description: "Mixed Negative Values", description: "Mixed Negative Values",
values: vec![decimal(-13.5), decimal(-11.5), int(10)], values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(decimal(-5)), Ok(decimal(-5)),
Ok(decimal(-13.5)), Ok(decimal_from_float(-13.5)),
Ok(int(10)), Ok(int(10)),
Ok(decimal(-11.5)), Ok(decimal_from_float(-11.5)),
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])), Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))), Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
Ok(decimal(-15)), Ok(decimal(-15)),
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))), Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
@ -151,10 +151,10 @@ mod tests {
], ],
expected_err: None, expected_err: None,
expected_res: vec![ expected_res: vec![
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]), Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]), Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]), Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]), Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
Ok(row![ Ok(row![
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]), "col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)]) "col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
@ -164,7 +164,7 @@ mod tests {
"col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string")) "col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string"))
]), ]),
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]), Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
Ok(row!["col1".to_owned() => decimal(1.25), "col2".to_owned() => decimal(1.25)]), Ok(row!["col1".to_owned() => decimal_from_float(1.25), "col2".to_owned() => decimal_from_float(1.25)]),
], ],
}, },
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved // TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved

View File

@ -41,7 +41,11 @@ impl WholeStreamCommand for SubCommand {
vec![Example { vec![Example {
description: "Evalulate math in the pipeline", description: "Evalulate math in the pipeline",
example: "echo '10 / 4' | math eval", example: "echo '10 / 4' | math eval",
result: Some(vec![UntaggedValue::decimal(2.5).into()]), result: Some(vec![UntaggedValue::decimal_from_float(
2.5,
Span::unknown(),
)
.into()]),
}] }]
} }
} }

View File

@ -50,7 +50,11 @@ impl WholeStreamCommand for SubCommand {
vec![Example { vec![Example {
description: "Get the median of a list of numbers", description: "Get the median of a list of numbers",
example: "echo [3 8 9 12 12 15] | math median", example: "echo [3 8 9 12 12 15] | math median",
result: Some(vec![UntaggedValue::decimal(10.5).into()]), result: Some(vec![UntaggedValue::decimal_from_float(
10.5,
Span::unknown(),
)
.into()]),
}] }]
} }
} }

View File

@ -103,6 +103,7 @@ pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
&name.span, &name.span,
) )
}), }),
v if v.is_duration() => sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec()),
// v is nothing primitive // v is nothing primitive
v if v.is_none() => sum( v if v.is_none() => sum(
UntaggedValue::int(0).into_untagged_value(), UntaggedValue::int(0).into_untagged_value(),

View File

@ -99,12 +99,20 @@ impl WholeStreamCommand for SubCommand {
Example { Example {
description: "Get the variance of a list of numbers", description: "Get the variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance", example: "echo [1 2 3 4 5] | math variance",
result: Some(vec![UntaggedValue::decimal(2).into()]), result: Some(vec![UntaggedValue::decimal_from_float(
2.0,
Span::unknown(),
)
.into()]),
}, },
Example { Example {
description: "Get the sample variance of a list of numbers", description: "Get the sample variance of a list of numbers",
example: "echo [1 2 3 4 5] | math variance -s", example: "echo [1 2 3 4 5] | math variance -s",
result: Some(vec![UntaggedValue::decimal(2.5).into()]), result: Some(vec![UntaggedValue::decimal_from_float(
2.5,
Span::unknown(),
)
.into()]),
}, },
] ]
} }

View File

@ -1,6 +1,6 @@
use crate::command_registry::CommandRegistry;
use crate::commands::classified::block::run_block; use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_data::value::merge_values; use nu_data::value::merge_values;
@ -55,7 +55,7 @@ async fn merge(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let mut context = Context::from_raw(&raw_args, &registry); let mut context = EvaluationContext::from_raw(&raw_args, &registry);
let name_tag = raw_args.call_info.name_tag.clone(); let name_tag = raw_args.call_info.name_tag.clone();
let (merge_args, input): (MergeArgs, _) = raw_args.process(&registry).await?; let (merge_args, input): (MergeArgs, _) = raw_args.process(&registry).await?;
let block = merge_args.block; let block = merge_args.block;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_data::base::select_fields; use nu_data::base::select_fields;
use nu_errors::ShellError; use nu_errors::ShellError;

View File

@ -17,7 +17,7 @@ impl WholeStreamCommand for Command {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"moves across desired subcommand." "Moves across desired subcommand."
} }
async fn run( async fn run(

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};

View File

@ -0,0 +1,3 @@
mod plugin;
pub use plugin::SubCommand as NuPlugin;

View File

@ -0,0 +1,124 @@
use std::path::PathBuf;
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::path::canonicalize;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
#[serde(rename = "load")]
pub load_path: Option<Tagged<PathBuf>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"nu plugin"
}
fn signature(&self) -> Signature {
Signature::build("nu plugin").named(
"load",
SyntaxShape::Path,
"a path to load the plugins from",
Some('l'),
)
}
fn usage(&self) -> &str {
"Nu Plugin"
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Load all plugins in the current directory",
example: "nu plugin --load .",
result: Some(vec![]),
}]
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let shell_manager = args.shell_manager.clone();
let (Arguments { load_path }, _) = args.process(&registry).await?;
if let Some(Tagged {
item: load_path,
tag,
}) = load_path
{
let path = canonicalize(shell_manager.path(), load_path).map_err(|_| {
ShellError::labeled_error(
"Cannot load plugins from directory",
"directory not found",
&tag,
)
})?;
if !path.is_dir() {
return Err(ShellError::labeled_error(
"Cannot load plugins from directory",
"is not a directory",
&tag,
));
}
#[cfg(unix)]
{
let has_exec = path
.metadata()
.map(|m| umask::Mode::from(m.permissions().mode()).has(umask::USER_READ))
.map_err(|e| {
ShellError::labeled_error(
"Cannot load plugins from directory",
format!("cannot stat ({})", e),
&tag,
)
})?;
if !has_exec {
return Err(ShellError::labeled_error(
"Cannot load plugins from directory",
"permission denied",
&tag,
));
}
}
return Ok(vec![ReturnSuccess::action(CommandAction::AddPlugins(
path.to_string_lossy().to_string(),
))]
.into());
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(crate::commands::help::get_help(&SubCommand, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};

View File

@ -0,0 +1,112 @@
use crate::commands::WholeStreamCommand;
use crate::deserializer::NumericRange;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct IntegerArgs {
range: Option<Tagged<NumericRange>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"random integer"
}
fn signature(&self) -> Signature {
Signature::build("random integer").optional("range", SyntaxShape::Range, "Range of values")
}
fn usage(&self) -> &str {
"Generate a random integer [min..max]"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
integer(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Generate an unconstrained random integer",
example: "random integer",
result: None,
},
Example {
description: "Generate a random integer less than or equal to 500",
example: "random integer ..500",
result: None,
},
Example {
description: "Generate a random integer greater than or equal to 100000",
example: "random integer 100000..",
result: None,
},
Example {
description: "Generate a random integer between 1 and 10",
example: "random integer 1..10",
result: None,
},
]
}
}
pub async fn integer(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let (IntegerArgs { range }, _) = args.process(&registry).await?;
let (min, max) = if let Some(range) = &range {
(range.item.min(), range.item.max())
} else {
(0, u64::MAX)
};
match min.cmp(&max) {
Ordering::Greater => Err(ShellError::labeled_error(
format!("Invalid range {}..{}", min, max),
"expected a valid range",
range
.expect("Unexpected ordering error in random integer")
.span(),
)),
Ordering::Equal => {
let untagged_result = UntaggedValue::int(min).into_value(Tag::unknown());
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
}
_ => {
let mut thread_rng = thread_rng();
// add 1 to max, because gen_range is right-exclusive
let max = max.saturating_add(1);
let result: u64 = thread_rng.gen_range(min, max);
let untagged_result = UntaggedValue::int(result).into_value(Tag::unknown());
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
}
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -2,6 +2,7 @@ pub mod command;
pub mod bool; pub mod bool;
pub mod dice; pub mod dice;
pub mod integer;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
pub mod uuid; pub mod uuid;
@ -9,5 +10,6 @@ pub use command::Command as Random;
pub use self::bool::SubCommand as RandomBool; pub use self::bool::SubCommand as RandomBool;
pub use dice::SubCommand as RandomDice; pub use dice::SubCommand as RandomDice;
pub use integer::SubCommand as RandomInteger;
#[cfg(feature = "uuid_crate")] #[cfg(feature = "uuid_crate")]
pub use uuid::SubCommand as RandomUUID; pub use uuid::SubCommand as RandomUUID;

View File

@ -1,9 +1,9 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::deserializer::NumericRange; use crate::deserializer::NumericRange;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape}; use nu_protocol::{RangeInclusion, ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged; use nu_source::Tagged;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -44,11 +44,23 @@ async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
let registry = registry.clone(); let registry = registry.clone();
let (RangeArgs { area }, input) = args.process(&registry).await?; let (RangeArgs { area }, input) = args.process(&registry).await?;
let range = area.item; let range = area.item;
let (from, _) = range.from; let (from, left_inclusive) = range.from;
let (to, _) = range.to; let (to, right_inclusive) = range.to;
let from = from.map(|from| *from as usize).unwrap_or(0).saturating_add(
let from = *from as usize; if left_inclusive == RangeInclusion::Inclusive {
let to = *to as usize; 0
} else {
1
},
);
let to = to
.map(|to| *to as usize)
.unwrap_or(usize::MAX)
.saturating_sub(if right_inclusive == RangeInclusion::Inclusive {
0
} else {
1
});
Ok(input Ok(input
.skip(from) .skip(from)

View File

@ -81,7 +81,7 @@ impl WholeStreamCommand for Reduce {
async fn process_row( async fn process_row(
block: Arc<Block>, block: Arc<Block>,
scope: Arc<Scope>, scope: Arc<Scope>,
mut context: Arc<Context>, mut context: Arc<EvaluationContext>,
row: Value, row: Value,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let row_clone = row.clone(); let row_clone = row.clone();
@ -104,7 +104,7 @@ async fn reduce(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let registry = registry.clone(); let registry = registry.clone();
let base_scope = raw_args.call_info.scope.clone(); let base_scope = raw_args.call_info.scope.clone();
let context = Arc::new(Context::from_raw(&raw_args, &registry)); let context = Arc::new(EvaluationContext::from_raw(&raw_args, &registry));
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process(&registry).await?; let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process(&registry).await?;
let block = Arc::new(reduce_args.block); let block = Arc::new(reduce_args.block);
let (ioffset, start) = match reduce_args.fold { let (ioffset, start) = match reduce_args.fold {
@ -138,7 +138,19 @@ async fn reduce(
let row = each::make_indexed_item(input.0 + ioffset, input.1); let row = each::make_indexed_item(input.0 + ioffset, input.1);
async { async {
let f = acc?.into_vec().await[0].clone(); let values = acc?.drain_vec().await;
let f = if values.len() == 1 {
let value = values
.get(0)
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
value.clone()
} else if values.is_empty() {
UntaggedValue::nothing().into_untagged_value()
} else {
UntaggedValue::table(&values).into_untagged_value()
};
scope.vars.insert(String::from("$acc"), f); scope.vars.insert(String::from("$acc"), f);
process_row(block, Arc::new(scope), context, row).await process_row(block, Arc::new(scope), context, row).await
} }
@ -154,9 +166,20 @@ async fn reduce(
let context = Arc::clone(&context); let context = Arc::clone(&context);
async { async {
scope let values = acc?.drain_vec().await;
.vars
.insert(String::from("$acc"), acc?.into_vec().await[0].clone()); let f = if values.len() == 1 {
let value = values
.get(0)
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
value.clone()
} else if values.is_empty() {
UntaggedValue::nothing().into_untagged_value()
} else {
UntaggedValue::table(&values).into_untagged_value()
};
scope.vars.insert(String::from("$acc"), f);
process_row(block, Arc::new(scope), context, row).await process_row(block, Arc::new(scope), context, row).await
} }
}) })

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape};

View File

@ -44,7 +44,7 @@ impl WholeStreamCommand for AliasCommand {
block.set_redirect(call_info.args.external_redirection); block.set_redirect(call_info.args.external_redirection);
let alias_command = self.clone(); let alias_command = self.clone();
let mut context = Context::from_args(&args, &registry); let mut context = EvaluationContext::from_args(&args, &registry);
let input = args.input; let input = args.input;
let mut scope = call_info.scope.clone(); let mut scope = call_info.scope.clone();

View File

@ -48,7 +48,19 @@ impl WholeStreamCommand for RunExternalCommand {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"" "Runs external command (not a nushell builtin)"
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Run the external echo command",
example: "run_external echo 'nushell'",
result: None,
}]
}
fn is_internal(&self) -> bool {
true
} }
async fn run( async fn run(
@ -72,33 +84,16 @@ impl WholeStreamCommand for RunExternalCommand {
.and_then(spanned_expression_to_string)?; .and_then(spanned_expression_to_string)?;
let mut external_context = { let mut external_context = {
#[cfg(windows)] EvaluationContext {
{
Context {
registry: registry.clone(), registry: registry.clone(),
host: args.host.clone(), host: args.host.clone(),
user_recently_used_autoenv_untrust: false, user_recently_used_autoenv_untrust: false,
shell_manager: args.shell_manager.clone(), shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(), ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])), current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new( windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
std::collections::HashMap::new(),
)),
raw_input: String::default(), raw_input: String::default(),
} }
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
user_recently_used_autoenv_untrust: false,
host: args.host.clone(),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
raw_input: String::default(),
}
}
}; };
let is_interactive = self.interactive; let is_interactive = self.interactive;
@ -148,7 +143,10 @@ impl WholeStreamCommand for RunExternalCommand {
} }
#[allow(unused_variables)] #[allow(unused_variables)]
async fn maybe_autocd_dir<'a>(cmd: &ExternalCommand, ctx: &mut Context) -> Option<String> { async fn maybe_autocd_dir<'a>(
cmd: &ExternalCommand,
ctx: &mut EvaluationContext,
) -> Option<String> {
// We will "auto cd" if // We will "auto cd" if
// - the command name ends in a path separator, or // - the command name ends in a path separator, or
// - it's not a command on the path and no arguments were given. // - it's not a command on the path and no arguments were given.

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ use nu_protocol::{
@ -83,7 +83,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
let fetcher = get_data_by_column_path( let fetcher = get_data_by_column_path(
&value, &value,
&path, &path,
Box::new(move |(obj_source, path_member_tried, error)| { move |obj_source, path_member_tried, error| {
if let PathMember { if let PathMember {
unspanned: UnspannedPathMember::String(column), unspanned: UnspannedPathMember::String(column),
.. ..
@ -98,7 +98,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
} }
error error
}), },
); );
let field = path.clone(); let field = path.clone();

View File

@ -37,9 +37,9 @@ fn shells(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) { if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
dict.insert_untagged("active", "X".to_string()); dict.insert_untagged("active", true);
} else { } else {
dict.insert_untagged("active", " ".to_string()); dict.insert_untagged("active", false);
} }
dict.insert_untagged("name", shell.name()); dict.insert_untagged("name", shell.name());
dict.insert_untagged("path", shell.path()); dict.insert_untagged("path", shell.path());

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Value}; use nu_protocol::{ReturnSuccess, Value};

View File

@ -1,8 +1,11 @@
extern crate unicode_segmentation;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::prelude::*; use crate::prelude::*;
use indexmap::indexmap; use indexmap::indexmap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use unicode_segmentation::UnicodeSegmentation;
pub struct Size; pub struct Size;
@ -29,7 +32,8 @@ impl WholeStreamCommand for Size {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
Example {
description: "Count the number of words in a string", description: "Count the number of words in a string",
example: r#"echo "There are seven words in this sentence" | size"#, example: r#"echo "There are seven words in this sentence" | size"#,
result: Some(vec![UntaggedValue::row(indexmap! { result: Some(vec![UntaggedValue::row(indexmap! {
@ -39,7 +43,19 @@ impl WholeStreamCommand for Size {
"bytes".to_string() => UntaggedValue::int(38).into(), "bytes".to_string() => UntaggedValue::int(38).into(),
}) })
.into()]), .into()]),
}] },
Example {
description: "Counts unicode characters correctly in a string",
example: r#"echo "Amélie Amelie" | size"#,
result: Some(vec![UntaggedValue::row(indexmap! {
"lines".to_string() => UntaggedValue::int(0).into(),
"words".to_string() => UntaggedValue::int(2).into(),
"chars".to_string() => UntaggedValue::int(13).into(),
"bytes".to_string() => UntaggedValue::int(15).into(),
})
.into()]),
},
]
} }
} }
@ -72,15 +88,15 @@ fn count(contents: &str, tag: impl Into<Tag>) -> Value {
let bytes = contents.len() as i64; let bytes = contents.len() as i64;
let mut end_of_word = true; let mut end_of_word = true;
for c in contents.chars() { for c in UnicodeSegmentation::graphemes(contents, true) {
chars += 1; chars += 1;
match c { match c {
'\n' => { "\n" => {
lines += 1; lines += 1;
end_of_word = true; end_of_word = true;
} }
' ' => end_of_word = true, " " => end_of_word = true,
_ => { _ => {
if end_of_word { if end_of_word {
words += 1; words += 1;

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};

View File

@ -1,17 +1,26 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
use parking_lot::Mutex;
use std::{
future::Future,
pin::Pin,
sync::atomic::Ordering,
task::{Poll, Waker},
thread,
time::Duration,
};
use std::{thread, time}; const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
pub struct Sleep; pub struct Sleep;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SleepArgs { pub struct SleepArgs {
pub dur: Tagged<u64>, pub duration: Tagged<u64>,
pub rest: Vec<Tagged<u64>>, pub rest: Vec<Tagged<u64>>,
} }
@ -28,7 +37,7 @@ impl WholeStreamCommand for Sleep {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"delay for a specified amount of time" "Delay for a specified amount of time"
} }
async fn run( async fn run(
@ -36,7 +45,26 @@ impl WholeStreamCommand for Sleep {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
sleep(args, registry).await let registry = registry.clone();
let ctrl_c = args.ctrl_c().clone();
let (SleepArgs { duration, rest }, input) = args.process(&registry).await?;
let total_dur = Duration::from_nanos(duration.item)
+ rest
.iter()
.map(|val| Duration::from_nanos(val.item))
.sum::<Duration>();
SleepFuture::new(total_dur, ctrl_c).await;
// this is necessary because the following 2 commands gave different results:
// `echo | sleep 1sec` - nothing
// `sleep 1sec` - table with 0 elements
if input.is_empty() {
Ok(OutputStream::empty())
} else {
Ok(input.into())
}
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -51,20 +79,100 @@ impl WholeStreamCommand for Sleep {
example: "sleep 1sec 1sec 1sec", example: "sleep 1sec 1sec 1sec",
result: None, result: None,
}, },
Example {
description: "Delay the output of another command by 1sec",
example: "echo [55 120] | sleep 1sec",
result: Some(vec![
UntaggedValue::int(55).into(),
UntaggedValue::int(120).into(),
]),
},
] ]
} }
} }
async fn sleep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { struct SleepFuture {
let registry = registry.clone(); shared_state: Arc<Mutex<SharedState>>,
}
let (SleepArgs { dur, rest }, ..) = args.process(&registry).await?; impl SleepFuture {
/// Create a new `SleepFuture` which will complete after the provided
/// timeout and check for Ctrl+C periodically.
pub fn new(duration: Duration, ctrl_c: Arc<AtomicBool>) -> Self {
let shared_state = Arc::new(Mutex::new(SharedState {
done: false,
waker: None,
}));
let total_dur = dur.item + rest.iter().map(|val| val.item).sum::<u64>(); // Spawn the main sleep thread
let total_dur = time::Duration::from_nanos(total_dur); let thread_shared_state = shared_state.clone();
thread::sleep(total_dur); thread::spawn(move || {
thread::sleep(duration);
let mut shared_state = thread_shared_state.lock();
// Signal that the timer has completed and wake up the last
// task on which the future was polled, if one exists.
if !shared_state.done {
shared_state.done = true;
if let Some(waker) = shared_state.waker.take() {
waker.wake()
}
}
});
Ok(OutputStream::empty()) // Spawn the Ctrl+C-watching polling thread
let thread_shared_state = shared_state.clone();
thread::spawn(move || {
loop {
{
let mut shared_state = thread_shared_state.lock();
// exit if the main thread is done
if shared_state.done {
return;
}
// finish the future prematurely if Ctrl+C has been pressed
if ctrl_c.load(Ordering::SeqCst) {
shared_state.done = true;
if let Some(waker) = shared_state.waker.take() {
waker.wake()
}
return;
}
}
// sleep for a short time
thread::sleep(CTRL_C_CHECK_INTERVAL);
}
});
SleepFuture { shared_state }
}
}
struct SharedState {
done: bool,
waker: Option<Waker>,
}
impl Future for SleepFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
// Look at the shared state to see if the timer has already completed.
let mut shared_state = self.shared_state.lock();
if shared_state.done {
Poll::Ready(())
} else {
// Set the waker if necessary
if shared_state
.waker
.as_ref()
.map(|waker| !waker.will_wake(&cx.waker()))
.unwrap_or(true)
{
shared_state.waker = Some(cx.waker().clone());
}
Poll::Pending
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -73,7 +181,6 @@ mod tests {
use std::time::Instant; use std::time::Instant;
#[test] #[test]
#[ignore]
fn examples_work_as_expected() { fn examples_work_as_expected() {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
@ -81,6 +188,8 @@ mod tests {
test_examples(Sleep {}); test_examples(Sleep {});
let elapsed = start.elapsed(); let elapsed = start.elapsed();
println!("{:?}", elapsed); println!("{:?}", elapsed);
assert!(elapsed >= std::time::Duration::from_secs(4)); // only examples with actual output are run
assert!(elapsed >= std::time::Duration::from_secs(1));
assert!(elapsed < std::time::Duration::from_secs(2));
} }
} }

View File

@ -130,7 +130,7 @@ fn action(
mod tests { mod tests {
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string; use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue}; use nu_protocol::UntaggedValue;
use nu_source::Tag; use nu_source::Tag;
#[test] #[test]
@ -145,8 +145,7 @@ mod tests {
let word = string("Cargo.tomL"); let word = string("Cargo.tomL");
let pattern = ".tomL"; let pattern = ".tomL";
let insensitive = false; let insensitive = false;
let expected = let expected = UntaggedValue::boolean(true).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -157,8 +156,7 @@ mod tests {
let word = string("Cargo.tomL"); let word = string("Cargo.tomL");
let pattern = "Lomt."; let pattern = "Lomt.";
let insensitive = false; let insensitive = false;
let expected = let expected = UntaggedValue::boolean(false).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -169,8 +167,7 @@ mod tests {
let word = string("Cargo.ToMl"); let word = string("Cargo.ToMl");
let pattern = ".TOML"; let pattern = ".TOML";
let insensitive = true; let insensitive = true;
let expected = let expected = UntaggedValue::boolean(true).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -181,8 +178,7 @@ mod tests {
let word = string("Cargo.tOml"); let word = string("Cargo.tOml");
let pattern = "lomt."; let pattern = "lomt.";
let insensitive = true; let insensitive = true;
let expected = let expected = UntaggedValue::boolean(false).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -104,7 +104,7 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
mod tests { mod tests {
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string; use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue}; use nu_protocol::UntaggedValue;
use nu_source::Tag; use nu_source::Tag;
#[test] #[test]
@ -118,8 +118,7 @@ mod tests {
fn str_ends_with_pattern() { fn str_ends_with_pattern() {
let word = string("Cargo.toml"); let word = string("Cargo.toml");
let pattern = ".toml"; let pattern = ".toml";
let expected = let expected = UntaggedValue::boolean(true).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -129,8 +128,7 @@ mod tests {
fn str_does_not_end_with_pattern() { fn str_does_not_end_with_pattern() {
let word = string("Cargo.toml"); let word = string("Cargo.toml");
let pattern = "Car"; let pattern = "Car";
let expected = let expected = UntaggedValue::boolean(false).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -7,7 +7,6 @@ use nu_protocol::{
}; };
use nu_source::{Tag, Tagged}; use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
use regex::Regex; use regex::Regex;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -15,6 +14,7 @@ struct Arguments {
find: Tagged<String>, find: Tagged<String>,
replace: Tagged<String>, replace: Tagged<String>,
rest: Vec<ColumnPath>, rest: Vec<ColumnPath>,
all: bool,
} }
pub struct SubCommand; pub struct SubCommand;
@ -33,6 +33,7 @@ impl WholeStreamCommand for SubCommand {
SyntaxShape::ColumnPath, SyntaxShape::ColumnPath,
"optionally find and replace text by column paths", "optionally find and replace text by column paths",
) )
.switch("all", "replace all occurences of find string", Some('a'))
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -48,11 +49,18 @@ impl WholeStreamCommand for SubCommand {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
Example {
description: "Find and replace contents with capture group", description: "Find and replace contents with capture group",
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'", example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'",
result: Some(vec![Value::from("my_library.nu")]), result: Some(vec![Value::from("my_library.nu")]),
}] },
Example {
description: "Find and replace all occurrences of find string",
example: "echo 'abc abc abc' | str find-replace -a 'b' 'z'",
result: Some(vec![Value::from("azc azc azc")]),
},
]
} }
} }
@ -70,17 +78,17 @@ async fn operate(
find, find,
replace, replace,
rest, rest,
all,
}, },
input, input,
) = args.process(&registry).await?; ) = args.process(&registry).await?;
let options = FindReplace(find.item, replace.item); let options = FindReplace(find.item, replace.item);
let column_paths: Vec<_> = rest; let column_paths: Vec<_> = rest;
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
ReturnSuccess::value(action(&v, &options, v.tag())?) ReturnSuccess::value(action(&v, &options, v.tag(), all)?)
} else { } else {
let mut ret = v; let mut ret = v;
@ -89,7 +97,7 @@ async fn operate(
ret = ret.swap_data_by_column_path( ret = ret.swap_data_by_column_path(
path, path,
Box::new(move |old| action(old, &options, old.tag())), Box::new(move |old| action(old, &options, old.tag(), all)),
)?; )?;
} }
@ -99,7 +107,12 @@ async fn operate(
.to_output_stream()) .to_output_stream())
} }
fn action(input: &Value, options: &FindReplace, tag: impl Into<Tag>) -> Result<Value, ShellError> { fn action(
input: &Value,
options: &FindReplace,
tag: impl Into<Tag>,
all: bool,
) -> Result<Value, ShellError> {
match &input.value { match &input.value {
UntaggedValue::Primitive(Primitive::Line(s)) UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => { | UntaggedValue::Primitive(Primitive::String(s)) => {
@ -109,7 +122,13 @@ fn action(input: &Value, options: &FindReplace, tag: impl Into<Tag>) -> Result<V
let regex = Regex::new(find.as_str()); let regex = Regex::new(find.as_str());
let out = match regex { let out = match regex {
Ok(re) => UntaggedValue::string(re.replace(s, replacement.as_str()).to_owned()), Ok(re) => {
if all {
UntaggedValue::string(re.replace_all(s, replacement.as_str()).to_owned())
} else {
UntaggedValue::string(re.replace(s, replacement.as_str()).to_owned())
}
}
Err(_) => UntaggedValue::string(s), Err(_) => UntaggedValue::string(s),
}; };
@ -143,10 +162,10 @@ mod tests {
fn can_have_capture_groups() { fn can_have_capture_groups() {
let word = string("Cargo.toml"); let word = string("Cargo.toml");
let expected = string("Carga.toml"); let expected = string("Carga.toml");
let all = false;
let find_replace_options = FindReplace("Cargo.(.+)".to_string(), "Carga.$1".to_string()); let find_replace_options = FindReplace("Cargo.(.+)".to_string(), "Carga.$1".to_string());
let actual = action(&word, &find_replace_options, Tag::unknown()).unwrap(); let actual = action(&word, &find_replace_options, Tag::unknown(), all).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
} }

View File

@ -8,7 +8,7 @@ use nu_source::Tagged;
use num_bigint::{BigInt, BigUint, ToBigInt}; use num_bigint::{BigInt, BigUint, ToBigInt};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml) // TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
use num_format::{Locale, ToFormattedString}; use num_format::Locale;
use num_traits::{Pow, Signed}; use num_traits::{Pow, Signed};
use std::iter; use std::iter;
@ -40,12 +40,15 @@ impl WholeStreamCommand for SubCommand {
"decimal digits to which to round", "decimal digits to which to round",
Some('d'), Some('d'),
) )
/*
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
.switch( .switch(
"group-digits", "group-digits",
// TODO according to system localization // TODO according to system localization
"group digits, currently by thousand with commas", "group digits, currently by thousand with commas",
Some('g'), Some('g'),
) )
*/
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -67,6 +70,8 @@ impl WholeStreamCommand for SubCommand {
example: "= 1.7 | str from -d 0", example: "= 1.7 | str from -d 0",
result: Some(vec![UntaggedValue::string("2").into_untagged_value()]), result: Some(vec![UntaggedValue::string("2").into_untagged_value()]),
}, },
/*
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
Example { Example {
description: "format large number with localized digit grouping", description: "format large number with localized digit grouping",
example: "= 1000000.2 | str from -g", example: "= 1000000.2 | str from -g",
@ -74,6 +79,7 @@ impl WholeStreamCommand for SubCommand {
UntaggedValue::string("1,000,000.2").into_untagged_value() UntaggedValue::string("1,000,000.2").into_untagged_value()
]), ]),
}, },
*/
] ]
} }
} }
@ -147,7 +153,7 @@ pub fn action(
} }
fn format_bigint(int: &BigInt) -> String { fn format_bigint(int: &BigInt) -> String {
int.to_formatted_string(&Locale::en) format!("{}", int)
// TODO once platform-specific dependencies are stable (see Cargo.toml) // TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)] // #[cfg(windows)]
@ -200,10 +206,8 @@ fn format_decimal(mut decimal: BigDecimal, digits: Option<u64>, group_digits: bo
let format_default_loc = |int_part: BigInt| { let format_default_loc = |int_part: BigInt| {
let loc = Locale::en; let loc = Locale::en;
let (int_str, sep) = ( //TODO: when num_format is available for recent bigint, replace this with the locale-based format
int_part.to_formatted_string(&loc), let (int_str, sep) = (format!("{}", int_part), String::from(loc.decimal()));
String::from(loc.decimal()),
);
format!("{}{}{}", int_str, sep, dec_str) format!("{}{}{}", int_str, sep, dec_str)
}; };

View File

@ -104,7 +104,7 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
mod tests { mod tests {
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string; use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue}; use nu_protocol::UntaggedValue;
use nu_source::Tag; use nu_source::Tag;
#[test] #[test]
@ -118,8 +118,7 @@ mod tests {
fn str_starts_with_pattern() { fn str_starts_with_pattern() {
let word = string("Cargo.toml"); let word = string("Cargo.toml");
let pattern = "Car"; let pattern = "Car";
let expected = let expected = UntaggedValue::boolean(true).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -129,8 +128,7 @@ mod tests {
fn str_does_not_start_with_pattern() { fn str_does_not_start_with_pattern() {
let word = string("Cargo.toml"); let word = string("Cargo.toml");
let pattern = ".toml"; let pattern = ".toml";
let expected = let expected = UntaggedValue::boolean(false).into_untagged_value();
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap(); let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -8,7 +8,6 @@ use nu_protocol::{
use nu_source::Tag; use nu_source::Tag;
use nu_value_ext::{as_string, ValueExt}; use nu_value_ext::{as_string, ValueExt};
use std::cmp;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::TryInto; use std::convert::TryInto;
@ -64,9 +63,9 @@ impl WholeStreamCommand for SubCommand {
result: Some(vec![Value::from("nushell")]), result: Some(vec![Value::from("nushell")]),
}, },
Example { Example {
description: "Get the last characters from the string", description: "Drop the last `n` characters from the string",
example: "echo 'good nushell' | str substring ',-5'", example: "echo 'good nushell' | str substring ',-5'",
result: Some(vec![Value::from("shell")]), result: Some(vec![Value::from("good nu")]),
}, },
Example { Example {
description: "Get the remaining characters from a starting index", description: "Get the remaining characters from a starting index",
@ -141,8 +140,16 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
) )
})?; })?;
let start: isize = options.0; let start: isize = if options.0 < 0 {
let end: isize = options.1; options.0 + len
} else {
options.0
};
let end: isize = if options.1 < 0 {
std::cmp::max(len + options.1, 0)
} else {
options.1
};
if start < len && end >= 0 { if start < len && end >= 0 {
match start.cmp(&end) { match start.cmp(&end) {
@ -152,42 +159,14 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
"End must be greater than or equal to Start", "End must be greater than or equal to Start",
tag.span, tag.span,
)), )),
Ordering::Less => { Ordering::Less => Ok(UntaggedValue::string(
let end: isize = cmp::min(options.1, len);
Ok(UntaggedValue::string(
s.chars() s.chars()
.skip(start as usize) .skip(start as usize)
.take((end - start) as usize) .take((end - start) as usize)
.collect::<String>(), .collect::<String>(),
) )
.into_value(tag)) .into_value(tag)),
} }
}
} else if start >= 0 && end <= 0 {
let end = options.1.abs();
let reversed = s
.chars()
.skip(start as usize)
.take((len - start) as usize)
.collect::<String>();
let reversed = if start == 0 {
reversed
} else {
s.chars().take(start as usize).collect::<String>()
};
let reversed = reversed
.chars()
.rev()
.take(end as usize)
.collect::<String>();
Ok(
UntaggedValue::string(reversed.chars().rev().collect::<String>())
.into_value(tag),
)
} else { } else {
Ok(UntaggedValue::string("").into_value(tag)) Ok(UntaggedValue::string("").into_value(tag))
} }
@ -344,19 +323,20 @@ mod tests {
expectation("andr", (0, 4)), expectation("andr", (0, 4)),
expectation("andre", (0, 5)), expectation("andre", (0, 5)),
expectation("andres", (0, 6)), expectation("andres", (0, 6)),
expectation("andres", (0, -6)), expectation("", (0, -6)),
expectation("ndres", (0, -5)), expectation("a", (0, -5)),
expectation("dres", (0, -4)), expectation("an", (0, -4)),
expectation("res", (0, -3)), expectation("and", (0, -3)),
expectation("es", (0, -2)), expectation("andr", (0, -2)),
expectation("s", (0, -1)), expectation("andre", (0, -1)),
expectation("", (0, -110)),
expectation("", (6, 0)), expectation("", (6, 0)),
expectation("s", (6, -1)), expectation("", (6, -1)),
expectation("es", (6, -2)), expectation("", (6, -2)),
expectation("res", (6, -3)), expectation("", (6, -3)),
expectation("dres", (6, -4)), expectation("", (6, -4)),
expectation("ndres", (6, -5)), expectation("", (6, -5)),
expectation("andres", (6, -6)), expectation("", (6, -6)),
]; ];
for expectation in cases.iter() { for expectation in cases.iter() {

View File

@ -113,7 +113,7 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{action, SubCommand}; use super::{action, SubCommand};
use nu_plugin::test_helpers::value::{decimal, string}; use nu_plugin::test_helpers::value::{decimal_from_float, string};
use nu_source::Tag; use nu_source::Tag;
#[test] #[test]
@ -127,7 +127,7 @@ mod tests {
#[allow(clippy::approx_constant)] #[allow(clippy::approx_constant)]
fn turns_to_integer() { fn turns_to_integer() {
let word = string("3.1415"); let word = string("3.1415");
let expected = decimal(3.1415); let expected = decimal_from_float(3.1415);
let actual = action(&word, Tag::unknown()).unwrap(); let actual = action(&word, Tag::unknown()).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -8,6 +8,7 @@ use nu_protocol::ShellTypeName;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_source::{Tag, Tagged}; use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
use std::iter::FromIterator;
pub use trim_both_ends::SubCommand as Trim; pub use trim_both_ends::SubCommand as Trim;
pub use trim_left::SubCommand as TrimLeft; pub use trim_left::SubCommand as TrimLeft;
@ -38,14 +39,22 @@ where
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), to_trim, &trim_operation)?) ReturnSuccess::value(action(
&v,
v.tag(),
to_trim,
&trim_operation,
ActionMode::Global,
)?)
} else { } else {
let mut ret = v; let mut ret = v;
for path in &column_paths { for path in &column_paths {
ret = ret.swap_data_by_column_path( ret = ret.swap_data_by_column_path(
path, path,
Box::new(move |old| action(old, old.tag(), to_trim, &trim_operation)), Box::new(move |old| {
action(old, old.tag(), to_trim, &trim_operation, ActionMode::Local)
}),
)?; )?;
} }
@ -55,27 +64,63 @@ where
.to_output_stream()) .to_output_stream())
} }
#[derive(Debug, Copy, Clone)]
pub enum ActionMode {
Local,
Global,
}
pub fn action<F>( pub fn action<F>(
input: &Value, input: &Value,
tag: impl Into<Tag>, tag: impl Into<Tag>,
char_: Option<char>, char_: Option<char>,
trim_operation: &F, trim_operation: &F,
mode: ActionMode,
) -> Result<Value, ShellError> ) -> Result<Value, ShellError>
where where
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static, F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
{ {
let tag = tag.into();
match &input.value { match &input.value {
UntaggedValue::Primitive(Primitive::Line(s)) UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => { | UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag)) Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag))
} }
other => { other => match mode {
ActionMode::Global => match other {
UntaggedValue::Row(dictionary) => {
let results: Result<Vec<(String, Value)>, ShellError> = dictionary
.entries()
.iter()
.map(|(k, v)| -> Result<_, ShellError> {
Ok((
k.clone(),
action(&v, tag.clone(), char_, trim_operation, mode)?,
))
})
.collect();
let indexmap = IndexMap::from_iter(results?);
Ok(UntaggedValue::Row(indexmap.into()).into_value(tag))
}
UntaggedValue::Table(values) => {
let values: Result<Vec<Value>, ShellError> = values
.iter()
.map(|v| -> Result<_, ShellError> {
Ok(action(v, tag.clone(), char_, trim_operation, mode)?)
})
.collect();
Ok(UntaggedValue::Table(values?).into_value(tag))
}
_ => Ok(input.clone()),
},
ActionMode::Local => {
let got = format!("got {}", other.type_name()); let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error( Err(ShellError::labeled_error(
"value is not string", "value is not string",
got, got,
tag.into().span, tag.span,
)) ))
} }
},
} }
} }

View File

@ -63,8 +63,11 @@ fn trim(s: &str, char_: Option<char>) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{trim, SubCommand}; use super::{trim, SubCommand};
use crate::commands::str_::trim::action; use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::test_helpers::value::string; use nu_plugin::{
row,
test_helpers::value::{int, string, table},
};
use nu_source::Tag; use nu_source::Tag;
#[test] #[test]
@ -79,7 +82,43 @@ mod tests {
let word = string("andres "); let word = string("andres ");
let expected = string("andres"); let expected = string("andres");
let actual = action(&word, Tag::unknown(), None, &trim).unwrap(); let actual = action(&word, Tag::unknown(), None, &trim, ActionMode::Local).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn trims_global() {
let word = string(" global ");
let expected = string("global");
let actual = action(&word, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn global_trim_ignores_numbers() {
let number = int(2020);
let expected = int(2020);
let actual = action(&number, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn global_trim_row() {
let row = row!["a".to_string() => string(" c "), " b ".to_string() => string(" d ")];
let expected = row!["a".to_string() => string("c"), " b ".to_string() => string("d")];
let actual = action(&row, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn global_trim_table() {
let row = table(&[string(" a "), int(65), string(" d")]);
let expected = table(&[string("a"), int(65), string("d")]);
let actual = action(&row, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -88,7 +127,7 @@ mod tests {
let word = string("!#andres#!"); let word = string("!#andres#!");
let expected = string("#andres#"); let expected = string("#andres#");
let actual = action(&word, Tag::unknown(), Some('!'), &trim).unwrap(); let actual = action(&word, Tag::unknown(), Some('!'), &trim, ActionMode::Local).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
} }

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