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
condition: eq(variables['style'], 'canary')
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')
displayName: Run tests
- 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"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.19.0"
version = "0.20.0"
[workspace]
members = ["crates/*/"]
@ -18,48 +18,48 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"}
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
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_fetch = {version = "0.19.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_sqlite = {version = "0.19.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_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.19.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_sqlite = {version = "0.19.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_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", 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}
url = {version = "2.1.1", optional = true}
clap = "2.33.1"
ctrlc = "3.1.4"
clap = "2.33.3"
ctrlc = "3.1.6"
dunce = "1.0.1"
futures = {version = "0.3", features = ["compat", "io-compat"]}
log = "0.4.8"
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
log = "0.4.11"
pretty_env_logger = "0.4.0"
quick-xml = "0.18.1"
[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]
serde = {version = "1.0.110", features = ["derive"]}
serde = {version = "1.0.115", features = ["derive"]}
toml = "0.5.6"
[features]
@ -75,9 +75,11 @@ default = [
"ptree-support",
"term-support",
"uuid-support",
"rustyline-support",
"match",
"post",
"fetch",
"rich-benchmark",
]
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
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"]
git-support = ["nu-cli/git2"]
ptree-support = ["nu-cli/ptree"]
rich-benchmark = ["nu-cli/rich-benchmark"]
rustyline-support = ["nu-cli/rustyline-support"]
term-support = ["nu-cli/term"]
trash-support = ["nu-cli/trash-support"]
uuid-support = ["nu-cli/uuid_crate"]

View File

@ -4,103 +4,102 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.19.0"
version = "0.20.0"
[lib]
doctest = false
[dependencies]
nu-data = {version = "0.19.0", path = "../nu-data"}
nu-errors = {version = "0.19.0", path = "../nu-errors"}
nu-parser = {version = "0.19.0", path = "../nu-parser"}
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
nu-source = {version = "0.19.0", path = "../nu-source"}
nu-table = {version = "0.19.0", path = "../nu-table"}
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
nu-data = {version = "0.20.0", path = "../nu-data"}
nu-errors = {version = "0.20.0", path = "../nu-errors"}
nu-parser = {version = "0.20.0", path = "../nu-parser"}
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
nu-source = {version = "0.20.0", path = "../nu-source"}
nu-table = {version = "0.20.0", path = "../nu-table"}
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
ansi_term = "0.12.1"
app_dirs = {version = "2", package = "app_dirs2"}
async-recursion = "0.3.1"
async-trait = "0.1.36"
async-trait = "0.1.40"
base64 = "0.12.3"
bigdecimal = {version = "0.1.2", features = ["serde"]}
byte-unit = "3.1.3"
bytes = "0.5.5"
calamine = "0.16"
chrono = {version = "0.4.11", features = ["serde"]}
clap = "2.33.1"
bigdecimal = {version = "0.2.0", features = ["serde"]}
byte-unit = "4.0.9"
bytes = "0.5.6"
calamine = "0.16.1"
chrono = {version = "0.4.15", features = ["serde"]}
clap = "2.33.3"
codespan-reporting = "0.9.5"
csv = "1.1"
ctrlc = {version = "3.1.4", optional = true}
csv = "1.1.3"
ctrlc = {version = "3.1.6", optional = true}
derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true}
dirs = {version = "2.0.2", optional = true}
directories = {version = "3.0.1", optional = true}
dirs = {version = "3.0.1", optional = true}
dtparse = "1.1.0"
dunce = "1.0.1"
eml-parser = "0.1.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_codec = "0.4"
futures_codec = "0.4.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"
hex = "0.4"
heim = {version = "0.1.0-beta.3", optional = true}
htmlescape = "0.3.1"
ical = "0.6.*"
ical = "0.6.0"
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"
log = "0.4.8"
meval = "0.2"
log = "0.4.11"
meval = "0.2.0"
natural = "0.5.0"
num-bigint = {version = "0.2.6", features = ["serde"]}
num-format = {version = "0.4", features = ["with-num-bigint"]}
num-traits = "0.2.11"
num-bigint = {version = "0.3.0", features = ["serde"]}
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
num-traits = "0.2.12"
parking_lot = "0.11.0"
pin-utils = "0.1.0"
pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0"
ptree = {version = "0.2", optional = true}
pretty-hex = "0.2.0"
ptree = {version = "0.3.0", optional = true}
query_interface = "0.3.5"
rand = "0.7"
regex = "1"
rand = "0.7.3"
regex = "1.3.9"
roxmltree = "0.13.0"
rust-embed = "5.6.0"
rustyline = "6.2.0"
serde = {version = "1.0.114", features = ["derive"]}
rustyline = {version = "6.3.0", optional = true}
serde = {version = "1.0.115", features = ["derive"]}
serde-hjson = "0.9.1"
serde_bytes = "0.11.5"
serde_ini = "0.2.0"
serde_json = "1.0.55"
serde_urlencoded = "0.6.1"
serde_yaml = "0.8"
serde_json = "1.0.57"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.13"
sha2 = "0.9.1"
shellexpand = "2.0.0"
strip-ansi-escapes = "0.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"
termcolor = "1.1.0"
toml = "0.5.6"
typetag = "0.1.5"
umask = "1.0.0"
unicode-xid = "0.2.1"
unicode-segmentation = "1.6.0"
uom = {version = "0.28.0", features = ["f64", "try-from"]}
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], 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"
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]
umask = "1.0.0"
users = "0.10.0"
# TODO this will be possible with new dependency resolver
@ -112,17 +111,18 @@ users = "0.10.0"
[dependencies.rusqlite]
features = ["bundled", "blob"]
optional = true
version = "0.23.1"
version = "0.24.0"
[build-dependencies]
git2 = {version = "0.13", optional = true}
git2 = {version = "0.13.11", optional = true}
[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
[features]
clipboard-cli = ["clipboard"]
rich-benchmark = ["heim"]
rustyline-support = ["rustyline"]
stable = []
trash-support = ["trash"]

File diff suppressed because it is too large Load Diff

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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 {
let vars = config.vars.lock();
let vars = &config.vars;
if let Some(mode) = vars.get("pivot_mode") {
let mode = match mode.as_string() {

View File

@ -1,18 +1,22 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
#[cfg(feature = "rich-benchmark")]
use heim::cpu::time;
use nu_errors::ShellError;
use nu_protocol::{
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
};
use chrono::prelude::*;
use std::convert::TryInto;
use std::time::{Duration, Instant};
pub struct Benchmark;
#[derive(Deserialize, Debug)]
struct BenchmarkArgs {
block: Block,
passthrough: Option<Block>,
}
#[async_trait]
@ -22,15 +26,22 @@ impl WholeStreamCommand for Benchmark {
}
fn signature(&self) -> Signature {
Signature::build("benchmark").required(
"block",
SyntaxShape::Block,
"the block to run and benchmark",
)
Signature::build("benchmark")
.required(
"block",
SyntaxShape::Block,
"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 {
"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(
@ -40,6 +51,21 @@ impl WholeStreamCommand for Benchmark {
) -> Result<OutputStream, ShellError> {
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(
@ -48,11 +74,15 @@ async fn benchmark(
) -> Result<OutputStream, ShellError> {
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 (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(
&block,
@ -63,16 +93,110 @@ async fn benchmark(
&scope.env,
)
.await;
let output = result?.into_vec().await;
let _ = result?.drain_vec().await;
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
#[cfg(feature = "rich-benchmark")]
let end = time().await;
let end_time = Instant::now();
context.clear_errors();
let output = Ok(ReturnSuccess::Value(Value {
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
tag: Tag::from(block.span),
}));
// return basic runtime
#[cfg(not(feature = "rich-benchmark"))]
{
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()), // ⚡
"tilde" | "twiddle" | "squiggly" | "home" => 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,
}
}

View File

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

View File

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

View File

@ -19,7 +19,7 @@ use nu_source::Tag;
pub(crate) async fn run_external_command(
command: ExternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
scope: &Scope,
external_redirection: ExternalRedirection,
@ -39,7 +39,7 @@ pub(crate) async fn run_external_command(
async fn run_with_stdin(
command: ExternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
scope: &Scope,
external_redirection: ExternalRedirection,
@ -543,7 +543,7 @@ mod tests {
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
};
#[cfg(feature = "which")]
use super::{run_external_command, Context, InputStream};
use super::{run_external_command, EvaluationContext, InputStream};
#[cfg(feature = "which")]
use futures::executor::block_on;
@ -573,7 +573,8 @@ mod tests {
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
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(
cmd,
@ -591,7 +592,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> {
// 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)
// .await?
// .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(
command: InternalCommand,
context: &mut Context,
context: &mut EvaluationContext,
input: InputStream,
it: &Value,
vars: &IndexMap<String, Value>,
@ -200,6 +200,28 @@ pub(crate) async fn run_internal_command(
)]);
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 => {
context.shell_manager.prev();
InputStream::from_stream(futures::stream::iter(vec![]))

View File

@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
}
fn usage(&self) -> &str {
"clears the terminal"
"Clears the terminal"
}
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::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::StreamExt;
use nu_errors::ShellError;

View File

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

View File

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

View File

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

View File

@ -65,7 +65,7 @@ async fn do_(
let registry = registry.clone();
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 (
DoArgs {

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use futures::stream::once;
@ -84,7 +84,7 @@ pub async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
head: Arc<Box<SpannedExpression>>,
mut context: Arc<Context>,
mut context: Arc<EvaluationContext>,
input: Value,
) -> Result<OutputStream, ShellError> {
let input_clone = input.clone();
@ -120,7 +120,7 @@ async fn each(
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(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 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,
tag: Tag,
is_end_inclusive: bool,
is_done: bool,
}
impl 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 {
curr: range.from.0.item,
curr: start,
end: range.to.0.item,
tag,
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
is_done: false,
}
}
}
@ -99,14 +102,40 @@ impl RangeIterator {
impl Iterator for RangeIterator {
type Item = Result<ReturnSuccess, ShellError>;
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());
self.curr = match nu_data::value::compute_values(
let next_value = nu_data::value::compute_values(
Operator::Plus,
&UntaggedValue::Primitive(self.curr.clone()),
&UntaggedValue::int(1),
) {
);
self.curr = match next_value {
Ok(result) => match result {
UntaggedValue::Primitive(p) => p,
_ => {
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
}
};
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 {
// TODO: add inclusive/exclusive ranges
None

View File

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

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature};

View File

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

View File

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

View File

@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
.delimiter(separator as u8)
.from_reader(s.as_bytes());
let tag = tag.into();
let span = tag.span;
let headers = if headerless {
(1..=reader.headers()?.len())
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
if let Ok(i) = value.parse::<i64>() {
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
} 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 {
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 {
let tag = tag.into();
let span = tag.span;
match v {
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::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::I64(n) => UntaggedValue::int(*n).into_value(&tag),
serde_hjson::Value::String(s) => {

View File

@ -45,6 +45,7 @@ async fn from_ods(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone();
let (
@ -73,7 +74,7 @@ async fn from_ods(
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
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::Bool(b) => UntaggedValue::boolean(*b),
_ => 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 {
let tag = tag.into();
let span = tag.span;
match v {
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).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) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
}

View File

@ -45,6 +45,7 @@ async fn from_xlsx(
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let span = tag.span;
let registry = registry.clone();
let (
FromXLSXArgs {
@ -73,7 +74,7 @@ async fn from_xlsx(
let value = match cell {
DataType::Empty => UntaggedValue::nothing(),
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::Bool(b) => UntaggedValue::boolean(*b),
_ => UntaggedValue::nothing(),

View File

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

View File

@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
UnspannedPathMember, UntaggedValue, Value,
did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
};
use nu_source::HasFallibleSpan;
use nu_value_ext::get_data_by_column_path;
@ -58,195 +58,209 @@ impl WholeStreamCommand for Get {
}
}
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
let fields = path.clone();
get_data_by_column_path(
obj,
path,
Box::new(move |(obj_source, column_path_tried, error)| {
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
match &obj_source.value {
UntaggedValue::Table(rows) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
let suggestions: IndexSet<_> = rows
.iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned())
.collect();
let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![];
for row in rows {
for field in row.data_descriptors() {
if !existing_columns.contains(&field[..]) {
existing_columns.insert(field.clone());
names.push(field);
}
}
}
if names.is_empty() {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
"Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span),
);
} else {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(","),
names.join(",")
),
column_path_tried.span.since(path_members_span),
);
};
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
let total = rows.len();
let secondary_label = if total == 1 {
"The table only has 1 row".to_owned()
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
};
return ShellError::labeled_error_with_secondary(
"Row not found",
format!("There isn't a row indexed at {}", idx),
column_path_tried.span,
secondary_label,
column_path_tried.span.since(path_members_span),
);
}
},
UntaggedValue::Row(columns) => match column_path_tried {
PathMember {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
return ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1,
&obj_source.data_descriptors().join(",")
),
column_path_tried.span.since(path_members_span),
);
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
return ShellError::labeled_error_with_secondary(
"No rows available",
format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span,
format!(
"Appears to contain columns. Columns available: {}",
columns.keys().join(",")
),
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 (GetArgs { rest: column_paths }, mut input) = args.process(&registry).await?;
if column_paths.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
trace!("get {:?}", column_paths);
let output_stream = input
.map(move |item| {
let member = vec![member.clone()];
let column_paths = vec![&member, &fields]
.into_iter()
let output = column_paths
.iter()
.map(move |path| get_output(&item, path))
.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(),
)),
}
}
.collect::<Vec<_>>();
futures::stream::iter(output)
})
.flatten()
.to_output_stream())
.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 {
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 {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
let suggestions: IndexSet<_> = rows
.iter()
.filter_map(|r| did_you_mean(&r, &column_path_tried))
.map(|s| s[0].1.to_owned())
.collect();
let mut existing_columns: IndexSet<_> = IndexSet::default();
let mut names: Vec<String> = vec![];
for row in rows {
for field in row.data_descriptors() {
if !existing_columns.contains(&field[..]) {
existing_columns.insert(field.clone());
names.push(field);
}
}
}
if names.is_empty() {
ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
"Appears to contain rows. Try indexing instead.",
column_path_tried.span.since(path_members_span),
)
} else {
ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(","),
names.join(", ")
),
column_path_tried.span.since(path_members_span),
)
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => {
let total = rows.len();
let secondary_label = if total == 1 {
"The table only has 1 row".to_owned()
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
};
ShellError::labeled_error_with_secondary(
"Row not found",
format!("There isn't a row indexed at {}", idx),
column_path_tried.span,
secondary_label,
column_path_tried.span.since(path_members_span),
)
}
}
}
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 {
unspanned: UnspannedPathMember::String(column),
..
} => {
let primary_label = format!("There isn't a column named '{}'", &column);
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
Some(ShellError::labeled_error_with_secondary(
"Unknown column",
primary_label,
column_path_tried.span,
format!(
"Perhaps you meant '{}'? Columns available: {}",
suggestions[0].1,
&obj_source.data_descriptors().join(", ")
),
column_path_tried.span.since(path_members_span),
))
} else {
None
}
}
PathMember {
unspanned: UnspannedPathMember::Int(idx),
..
} => Some(ShellError::labeled_error_with_secondary(
"No rows available",
format!("A row at '{}' can't be indexed.", &idx),
column_path_tried.span,
format!(
"Appears to contain columns. Columns available: {}",
columns.keys().join(", ")
),
column_path_tried.span.since(path_members_span),
)),
}
}

View File

@ -28,7 +28,7 @@ impl WholeStreamCommand for GroupBy {
}
fn usage(&self) -> &str {
"create a new table grouped."
"Create a new table grouped."
}
async fn run(
@ -96,7 +96,7 @@ pub async fn group_by(
let registry = registry.clone();
let head = Arc::new(args.call_info.args.head.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 values: Vec<Value> = input.collect().await;

View File

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

View File

@ -63,54 +63,114 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
let mut sorted_names = registry.names();
sorted_names.sort();
Ok(
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
// If it's a subcommand, don't list it during the commands list
if cmd.contains(' ') {
return None;
}
let mut short_desc = TaggedDictBuilder::new(name.clone());
let document_tag = rest[0].tag.clone();
let value = command_dict(
match registry.get_command(&cmd).ok_or_else(|| {
let (mut subcommand_names, command_names) = sorted_names
.into_iter()
// Internal only commands shouldn't be displayed
.filter(|cmd_name| {
registry
.get_command(&cmd_name)
.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 value = command_dict(
registry.get_command(&cmd_name).ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd_name),
"could not load command",
document_tag,
)
})?,
name,
);
dict.insert_untagged("name", cmd_name);
dict.insert_untagged(
"description",
get_data_by_key(&value, "usage".spanned_unknown())
.ok_or_else(|| {
ShellError::labeled_error(
format!("Could not load {}", cmd),
"could not load command",
document_tag,
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
}) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
name.clone(),
);
})?
.as_string()?,
);
short_desc.insert_untagged("name", cmd);
short_desc.insert_untagged(
"description",
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|| {
ShellError::labeled_error(
"Expected a usage key",
"expected a 'usage' key",
&value.tag,
)
},
) {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
.as_string()
{
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
},
);
Ok(())
}
Some(ReturnSuccess::value(short_desc.into_value()))
}))
.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" {
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
&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| {
let path = by.clone();
let eval = nu_value_ext::get_data_by_column_path(
value,
&path,
Box::new(move |(_, _, error)| error),
);
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
match eval {
Ok(with_value) => Ok(with_value),

View File

@ -1,6 +1,6 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_data::config::NuConfig;
use nu_data::config::{Conf, NuConfig};
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use std::fs::File;
@ -9,9 +9,7 @@ use std::path::PathBuf;
const DEFAULT_LOCATION: &str = "history.txt";
pub fn history_path(config: &NuConfig) -> PathBuf {
let vars = config.vars.lock();
pub fn history_path(config: &dyn Conf) -> PathBuf {
let default_path = nu_data::config::user_data()
.map(|mut p| {
p.push(DEFAULT_LOCATION);
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
vars.get("history-path")
config
.var("history-path")
.map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() {
Ok(path) => PathBuf::from(path),
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
}
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 path = history_path(&config);
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::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
@ -74,7 +74,7 @@ async fn if_command(
let registry = Arc::new(registry.clone());
let scope = Arc::new(raw_args.call_info.scope.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 (
IfArgs {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,7 +53,11 @@ impl WholeStreamCommand for SubCommand {
vec![Example {
description: "Get the average of a list of numbers",
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: UntaggedValue::Primitive(other),
..

View File

@ -39,7 +39,7 @@ mod tests {
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
};
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 std::str::FromStr;
@ -98,17 +98,17 @@ mod tests {
},
TestCase {
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_res: vec![
Ok(decimal(21)),
Ok(int(10)),
Ok(decimal(26.5)),
Ok(decimal(26.5)),
Ok(table(&[decimal(26.5)])),
Ok(decimal_from_float(26.5)),
Ok(decimal_from_float(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(63)),
Ok(decimal(60.5)),
Ok(decimal_from_float(60.5)),
],
},
TestCase {
@ -128,14 +128,14 @@ mod tests {
},
TestCase {
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_res: vec![
Ok(decimal(-5)),
Ok(decimal(-13.5)),
Ok(decimal_from_float(-13.5)),
Ok(int(10)),
Ok(decimal(-11.5)),
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
Ok(decimal_from_float(-11.5)),
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(-15)),
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_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(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![
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
"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"))
]),
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

View File

@ -41,7 +41,11 @@ impl WholeStreamCommand for SubCommand {
vec![Example {
description: "Evalulate math in the pipeline",
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 {
description: "Get the median of a list of numbers",
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,
)
}),
v if v.is_duration() => sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec()),
// v is nothing primitive
v if v.is_none() => sum(
UntaggedValue::int(0).into_untagged_value(),

View File

@ -99,12 +99,20 @@ impl WholeStreamCommand for SubCommand {
Example {
description: "Get the variance of a list of numbers",
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 {
description: "Get the sample variance of a list of numbers",
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::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_data::value::merge_values;
@ -55,7 +55,7 @@ async fn merge(
) -> Result<OutputStream, ShellError> {
let registry = registry.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 (merge_args, input): (MergeArgs, _) = raw_args.process(&registry).await?;
let block = merge_args.block;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
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 dice;
pub mod integer;
#[cfg(feature = "uuid_crate")]
pub mod uuid;
@ -9,5 +10,6 @@ pub use command::Command as Random;
pub use self::bool::SubCommand as RandomBool;
pub use dice::SubCommand as RandomDice;
pub use integer::SubCommand as RandomInteger;
#[cfg(feature = "uuid_crate")]
pub use uuid::SubCommand as RandomUUID;

View File

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

View File

@ -81,7 +81,7 @@ impl WholeStreamCommand for Reduce {
async fn process_row(
block: Arc<Block>,
scope: Arc<Scope>,
mut context: Arc<Context>,
mut context: Arc<EvaluationContext>,
row: Value,
) -> Result<InputStream, ShellError> {
let row_clone = row.clone();
@ -104,7 +104,7 @@ async fn reduce(
) -> Result<OutputStream, ShellError> {
let registry = registry.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 block = Arc::new(reduce_args.block);
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);
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);
process_row(block, Arc::new(scope), context, row).await
}
@ -154,9 +166,20 @@ async fn reduce(
let context = Arc::clone(&context);
async {
scope
.vars
.insert(String::from("$acc"), 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);
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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};

View File

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

View File

@ -44,7 +44,7 @@ impl WholeStreamCommand for AliasCommand {
block.set_redirect(call_info.args.external_redirection);
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 mut scope = call_info.scope.clone();

View File

@ -48,7 +48,19 @@ impl WholeStreamCommand for RunExternalCommand {
}
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(
@ -72,32 +84,15 @@ impl WholeStreamCommand for RunExternalCommand {
.and_then(spanned_expression_to_string)?;
let mut external_context = {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: args.host.clone(),
user_recently_used_autoenv_untrust: false,
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(
std::collections::HashMap::new(),
)),
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(),
}
EvaluationContext {
registry: registry.clone(),
host: args.host.clone(),
user_recently_used_autoenv_untrust: false,
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
raw_input: String::default(),
}
};
@ -148,7 +143,10 @@ impl WholeStreamCommand for RunExternalCommand {
}
#[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
// - the command name ends in a path separator, or
// - 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::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
@ -83,7 +83,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
let fetcher = get_data_by_column_path(
&value,
&path,
Box::new(move |(obj_source, path_member_tried, error)| {
move |obj_source, path_member_tried, error| {
if let PathMember {
unspanned: UnspannedPathMember::String(column),
..
@ -98,7 +98,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
}
error
}),
},
);
let field = path.clone();

View File

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

View File

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

View File

@ -1,8 +1,11 @@
extern crate unicode_segmentation;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use indexmap::indexmap;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
use unicode_segmentation::UnicodeSegmentation;
pub struct Size;
@ -29,17 +32,30 @@ impl WholeStreamCommand for Size {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Count the number of words in a string",
example: r#"echo "There are seven words in this sentence" | size"#,
result: Some(vec![UntaggedValue::row(indexmap! {
"lines".to_string() => UntaggedValue::int(0).into(),
"words".to_string() => UntaggedValue::int(7).into(),
"chars".to_string() => UntaggedValue::int(38).into(),
"bytes".to_string() => UntaggedValue::int(38).into(),
})
.into()]),
}]
vec![
Example {
description: "Count the number of words in a string",
example: r#"echo "There are seven words in this sentence" | size"#,
result: Some(vec![UntaggedValue::row(indexmap! {
"lines".to_string() => UntaggedValue::int(0).into(),
"words".to_string() => UntaggedValue::int(7).into(),
"chars".to_string() => UntaggedValue::int(38).into(),
"bytes".to_string() => UntaggedValue::int(38).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 mut end_of_word = true;
for c in contents.chars() {
for c in UnicodeSegmentation::graphemes(contents, true) {
chars += 1;
match c {
'\n' => {
"\n" => {
lines += 1;
end_of_word = true;
}
' ' => end_of_word = true,
" " => end_of_word = true,
_ => {
if end_of_word {
words += 1;

View File

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

View File

@ -1,17 +1,26 @@
use crate::command_registry::CommandRegistry;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape};
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
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;
#[derive(Deserialize)]
pub struct SleepArgs {
pub dur: Tagged<u64>,
pub duration: Tagged<u64>,
pub rest: Vec<Tagged<u64>>,
}
@ -28,7 +37,7 @@ impl WholeStreamCommand for Sleep {
}
fn usage(&self) -> &str {
"delay for a specified amount of time"
"Delay for a specified amount of time"
}
async fn run(
@ -36,7 +45,26 @@ impl WholeStreamCommand for Sleep {
args: CommandArgs,
registry: &CommandRegistry,
) -> 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> {
@ -51,20 +79,100 @@ impl WholeStreamCommand for Sleep {
example: "sleep 1sec 1sec 1sec",
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> {
let registry = registry.clone();
struct SleepFuture {
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>();
let total_dur = time::Duration::from_nanos(total_dur);
thread::sleep(total_dur);
// Spawn the main sleep thread
let thread_shared_state = shared_state.clone();
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)]
@ -73,7 +181,6 @@ mod tests {
use std::time::Instant;
#[test]
#[ignore]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
@ -81,6 +188,8 @@ mod tests {
test_examples(Sleep {});
let elapsed = start.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 {
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
#[test]
@ -145,8 +145,7 @@ mod tests {
let word = string("Cargo.tomL");
let pattern = ".tomL";
let insensitive = false;
let expected =
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let expected = UntaggedValue::boolean(true).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
@ -157,8 +156,7 @@ mod tests {
let word = string("Cargo.tomL");
let pattern = "Lomt.";
let insensitive = false;
let expected =
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let expected = UntaggedValue::boolean(false).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
@ -169,8 +167,7 @@ mod tests {
let word = string("Cargo.ToMl");
let pattern = ".TOML";
let insensitive = true;
let expected =
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let expected = UntaggedValue::boolean(true).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
@ -181,8 +178,7 @@ mod tests {
let word = string("Cargo.tOml");
let pattern = "lomt.";
let insensitive = true;
let expected =
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let expected = UntaggedValue::boolean(false).into_untagged_value();
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
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 {
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
#[test]
@ -118,8 +118,7 @@ mod tests {
fn str_ends_with_pattern() {
let word = string("Cargo.toml");
let pattern = ".toml";
let expected =
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let expected = UntaggedValue::boolean(true).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
@ -129,8 +128,7 @@ mod tests {
fn str_does_not_end_with_pattern() {
let word = string("Cargo.toml");
let pattern = "Car";
let expected =
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let expected = UntaggedValue::boolean(false).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected);

View File

@ -7,7 +7,6 @@ use nu_protocol::{
};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
use regex::Regex;
#[derive(Deserialize)]
@ -15,6 +14,7 @@ struct Arguments {
find: Tagged<String>,
replace: Tagged<String>,
rest: Vec<ColumnPath>,
all: bool,
}
pub struct SubCommand;
@ -33,6 +33,7 @@ impl WholeStreamCommand for SubCommand {
SyntaxShape::ColumnPath,
"optionally find and replace text by column paths",
)
.switch("all", "replace all occurences of find string", Some('a'))
}
fn usage(&self) -> &str {
@ -48,11 +49,18 @@ impl WholeStreamCommand for SubCommand {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Find and replace contents with capture group",
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'",
result: Some(vec![Value::from("my_library.nu")]),
}]
vec![
Example {
description: "Find and replace contents with capture group",
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.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,
replace,
rest,
all,
},
input,
) = args.process(&registry).await?;
let options = FindReplace(find.item, replace.item);
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, &options, v.tag())?)
ReturnSuccess::value(action(&v, &options, v.tag(), all)?)
} else {
let mut ret = v;
@ -89,7 +97,7 @@ async fn operate(
ret = ret.swap_data_by_column_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())
}
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 {
UntaggedValue::Primitive(Primitive::Line(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 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),
};
@ -143,10 +162,10 @@ mod tests {
fn can_have_capture_groups() {
let word = string("Cargo.toml");
let expected = string("Carga.toml");
let all = false;
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);
}
}

View File

@ -8,7 +8,7 @@ use nu_source::Tagged;
use num_bigint::{BigInt, BigUint, ToBigInt};
// 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 std::iter;
@ -40,12 +40,15 @@ impl WholeStreamCommand for SubCommand {
"decimal digits to which to round",
Some('d'),
)
.switch(
"group-digits",
// TODO according to system localization
"group digits, currently by thousand with commas",
Some('g'),
)
/*
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
.switch(
"group-digits",
// TODO according to system localization
"group digits, currently by thousand with commas",
Some('g'),
)
*/
}
fn usage(&self) -> &str {
@ -67,6 +70,8 @@ impl WholeStreamCommand for SubCommand {
example: "= 1.7 | str from -d 0",
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 {
description: "format large number with localized digit grouping",
example: "= 1000000.2 | str from -g",
@ -74,6 +79,7 @@ impl WholeStreamCommand for SubCommand {
UntaggedValue::string("1,000,000.2").into_untagged_value()
]),
},
*/
]
}
}
@ -147,7 +153,7 @@ pub fn action(
}
fn format_bigint(int: &BigInt) -> String {
int.to_formatted_string(&Locale::en)
format!("{}", int)
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[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 loc = Locale::en;
let (int_str, sep) = (
int_part.to_formatted_string(&loc),
String::from(loc.decimal()),
);
//TODO: when num_format is available for recent bigint, replace this with the locale-based format
let (int_str, sep) = (format!("{}", int_part), String::from(loc.decimal()));
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 {
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_protocol::{Primitive, UntaggedValue};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
#[test]
@ -118,8 +118,7 @@ mod tests {
fn str_starts_with_pattern() {
let word = string("Cargo.toml");
let pattern = "Car";
let expected =
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
let expected = UntaggedValue::boolean(true).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
@ -129,8 +128,7 @@ mod tests {
fn str_does_not_start_with_pattern() {
let word = string("Cargo.toml");
let pattern = ".toml";
let expected =
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
let expected = UntaggedValue::boolean(false).into_untagged_value();
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
assert_eq!(actual, expected);

View File

@ -8,7 +8,6 @@ use nu_protocol::{
use nu_source::Tag;
use nu_value_ext::{as_string, ValueExt};
use std::cmp;
use std::cmp::Ordering;
use std::convert::TryInto;
@ -64,9 +63,9 @@ impl WholeStreamCommand for SubCommand {
result: Some(vec![Value::from("nushell")]),
},
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'",
result: Some(vec![Value::from("shell")]),
result: Some(vec![Value::from("good nu")]),
},
Example {
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 end: isize = options.1;
let start: isize = if options.0 < 0 {
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 {
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",
tag.span,
)),
Ordering::Less => {
let end: isize = cmp::min(options.1, len);
Ok(UntaggedValue::string(
s.chars()
.skip(start as usize)
.take((end - start) as usize)
.collect::<String>(),
)
.into_value(tag))
}
Ordering::Less => Ok(UntaggedValue::string(
s.chars()
.skip(start as usize)
.take((end - start) as usize)
.collect::<String>(),
)
.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 {
Ok(UntaggedValue::string("").into_value(tag))
}
@ -344,19 +323,20 @@ mod tests {
expectation("andr", (0, 4)),
expectation("andre", (0, 5)),
expectation("andres", (0, 6)),
expectation("andres", (0, -6)),
expectation("ndres", (0, -5)),
expectation("dres", (0, -4)),
expectation("res", (0, -3)),
expectation("es", (0, -2)),
expectation("s", (0, -1)),
expectation("", (0, -6)),
expectation("a", (0, -5)),
expectation("an", (0, -4)),
expectation("and", (0, -3)),
expectation("andr", (0, -2)),
expectation("andre", (0, -1)),
expectation("", (0, -110)),
expectation("", (6, 0)),
expectation("s", (6, -1)),
expectation("es", (6, -2)),
expectation("res", (6, -3)),
expectation("dres", (6, -4)),
expectation("ndres", (6, -5)),
expectation("andres", (6, -6)),
expectation("", (6, -1)),
expectation("", (6, -2)),
expectation("", (6, -3)),
expectation("", (6, -4)),
expectation("", (6, -5)),
expectation("", (6, -6)),
];
for expectation in cases.iter() {

View File

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

View File

@ -8,6 +8,7 @@ use nu_protocol::ShellTypeName;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
use std::iter::FromIterator;
pub use trim_both_ends::SubCommand as Trim;
pub use trim_left::SubCommand as TrimLeft;
@ -38,14 +39,22 @@ where
Ok(input
.map(move |v| {
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 {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_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())
}
#[derive(Debug, Copy, Clone)]
pub enum ActionMode {
Local,
Global,
}
pub fn action<F>(
input: &Value,
tag: impl Into<Tag>,
char_: Option<char>,
trim_operation: &F,
mode: ActionMode,
) -> Result<Value, ShellError>
where
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
{
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
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());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.span,
))
}
},
}
}

View File

@ -63,8 +63,11 @@ fn trim(s: &str, char_: Option<char>) -> String {
#[cfg(test)]
mod tests {
use super::{trim, SubCommand};
use crate::commands::str_::trim::action;
use nu_plugin::test_helpers::value::string;
use crate::commands::str_::trim::{action, ActionMode};
use nu_plugin::{
row,
test_helpers::value::{int, string, table},
};
use nu_source::Tag;
#[test]
@ -79,7 +82,43 @@ mod tests {
let word = 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);
}
@ -88,7 +127,7 @@ mod tests {
let word = 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);
}
}

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