mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 23:22:10 +02:00
Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
b674cee9d2 | |||
cb8491cfee | |||
8196b031f8 | |||
50dd56d3c4 | |||
0f7e1d4d01 | |||
ec77c572b9 | |||
f97561c416 | |||
5faa82e323 | |||
4e17292a12 | |||
666fbbb0d1 | |||
c6fe58467b | |||
46d1938f5c | |||
8229af7591 | |||
ee76523507 | |||
c283db373b | |||
1b0ed30516 | |||
a6fdee4a51 | |||
6951fb440c | |||
502c9ea706 | |||
22f67be461 | |||
77ffd06715 | |||
1d833ef972 | |||
0d8064ed2d | |||
cc06ea4d87 | |||
3cf7652e86 | |||
1eb28c6cb6 | |||
db590369a8 | |||
f4d654d2a2 | |||
5725e55abb | |||
b6d19cc9fa | |||
bc6c884a14 | |||
cb78bf8fd6 | |||
400bc97e35 | |||
2fd464bf7b | |||
e626522b3a | |||
791e07650d | |||
bf2363947b | |||
a2cc2259e7 | |||
808fe496a6 | |||
2fb48bd6ac | |||
2df8775b48 | |||
e02b4f1443 | |||
194782215f | |||
df17d28c0f | |||
5f43c8f024 | |||
1a18734f9a | |||
4a70c1ff4f | |||
770e5d89f2 | |||
cfac8e84dd | |||
43d90c1745 | |||
38bdb053d2 | |||
95e61773a5 | |||
4e931fa73f | |||
2573441e28 | |||
5770b15270 | |||
6817b472d0 | |||
2b076369e0 | |||
973a8ee8f3 | |||
1159d3365a | |||
152ba32eb7 | |||
153320ef33 | |||
ff236da72c | |||
54326869e4 | |||
f14f4e39c5 | |||
a18b2702ca | |||
93410c470e | |||
5d945ef869 | |||
df07be6a42 | |||
3c32d4947c | |||
2ea5235aea | |||
c096f031ce | |||
ae1d4bdb4c | |||
0adf2accdd | |||
4201f48be5 | |||
b076e375ca | |||
2f1016d44f | |||
ddf9d61346 | |||
f0b7ab5ecc | |||
e4c6336bd4 | |||
66061192f8 | |||
b7bc4c1f80 | |||
a56abb6502 | |||
892a416211 | |||
f45adecd01 | |||
bd015e82dc | |||
cf43b74f26 | |||
18909ec14a | |||
ed243c88d2 | |||
cb7723f423 | |||
9dc88f8a95 | |||
0a439fe52f | |||
a8b65e35ec | |||
bd9e598bf0 | |||
75f8247af1 | |||
e8ec5027ff | |||
ebba89ea31 | |||
8388afc9d9 | |||
b133724b38 | |||
09429d08aa | |||
9b577b8679 | |||
7a595827f1 | |||
332e12ded0 | |||
a508e15efe | |||
a5b6bb6209 | |||
1882a32b83 | |||
798766b4b5 | |||
193c4cc6d5 | |||
422b6ca871 | |||
2b13ac3856 | |||
4c10351579 | |||
dd27aaef1b | |||
6eb4a0e87b | |||
15f3a545f0 | |||
365f76ad19 | |||
df2845a9b4 | |||
8453261211 | |||
1dc8f3300e | |||
10d4edc7af | |||
50cbf91bc5 | |||
d05f9b3b1e | |||
f5fad393d0 | |||
d19a5f4c2f | |||
04451af776 | |||
232aca76a4 | |||
0178b53289 | |||
e05e6b42fe | |||
dd79afb503 | |||
599bb9797d | |||
c355585112 | |||
45f32c9541 | |||
7528094e12 | |||
dcfa135ab9 | |||
e9bb4f25eb | |||
0f7a9bbd31 | |||
73e65df5f6 | |||
a63a5adafa | |||
2eb4f8d28a | |||
d9ae66791a | |||
2c5939dc7d | |||
3150e70fc7 | |||
c9ffd6afc0 | |||
986b427038 | |||
c973850571 | |||
5a725f9651 | |||
79cc725aff | |||
e2cbc4e853 | |||
b5a27f0ccb | |||
bdb12f4bff | |||
c9c29f9e4c | |||
32951f1161 | |||
56f85b3108 | |||
16f85f32a2 | |||
2ae2f2ea9d | |||
4696c9069b | |||
1ffbb66e64 | |||
8dc7b8a7cd | |||
666e6a7b57 |
@ -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
|
||||
|
6
.cargo/config.toml
Normal file
6
.cargo/config.toml
Normal file
@ -0,0 +1,6 @@
|
||||
# use LLD as linker on Windows because it could be faster
|
||||
# for full and incremental builds compared to default
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
#linker = "lld-link.exe"
|
||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
@ -31,6 +31,11 @@ cargo build
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
- Build and run with extra features:
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
@ -60,3 +65,11 @@ cargo build
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
|
||||
```
|
||||
|
812
Cargo.lock
generated
812
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
155
Cargo.toml
155
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.19.0"
|
||||
version = "0.22.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,51 +18,58 @@ 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.22.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.22.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.22.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.22.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.22.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.22.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.22.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.22.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.22.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.22.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.22.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.22.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.22.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.22.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.22.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.22.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.22.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.22.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.22.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.22.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.22.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.22.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.22.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
|
||||
crossterm = {version = "0.17.5", optional = true}
|
||||
semver = {version = "0.10.0", optional = true}
|
||||
url = {version = "2.1.1", optional = true}
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.1"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
quick-xml = "0.18.1"
|
||||
itertools = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.19.0", path = "./crates/nu-test-support"}
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = {version = "0.22.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
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"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
@ -75,40 +82,39 @@ 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"]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"]
|
||||
stable = ["default"]
|
||||
|
||||
# Default
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["crossterm", "url", "nu_plugin_textview"]
|
||||
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
trace = ["nu-parser/trace"]
|
||||
|
||||
# Stable (Default)
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
inc = ["nu_plugin_inc"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
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"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -170,6 +176,41 @@ name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_bar"
|
||||
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_line"
|
||||
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_xpath"
|
||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||
required-features = ["xpath"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_bson"
|
||||
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_bson"
|
||||
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
@ -46,7 +46,7 @@ Try it in Gitpod.
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
|
||||
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.15.1
|
||||
version │ 0.21.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version | echo $it
|
||||
0.15.1
|
||||
> open Cargo.toml | get package.version
|
||||
0.21.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
|
@ -4,103 +4,104 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.19.0"
|
||||
version = "0.22.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.22.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.22.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.22.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.22.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.22.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.22.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.22.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}
|
||||
dtparse = "1.1.0"
|
||||
directories = {version = "3.0.1", optional = true}
|
||||
dirs = {version = "3.0.1", optional = true}
|
||||
dtparse = "1.2.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"
|
||||
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"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
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"
|
||||
quick-xml = "0.18.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"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
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}
|
||||
lazy_static = "1.*"
|
||||
|
||||
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"
|
||||
rayon = "1.4.0"
|
||||
trash = {version = "1.2.0", 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 +113,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"]
|
||||
|
@ -5,7 +5,7 @@ fn main() -> Result<(), io::Error> {
|
||||
let out_dir = env::var_os("OUT_DIR").expect(
|
||||
"\
|
||||
OUT_DIR environment variable not found. \
|
||||
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
|
||||
OUT_DIR is guaranteed to exist in a build script by cargo - see \
|
||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
||||
");
|
||||
|
||||
|
@ -1,38 +1,30 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::context::Context;
|
||||
use crate::git::current_branch;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan() {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
@ -64,21 +56,16 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn create_default_context(
|
||||
syncer: &mut crate::EnvironmentSyncer,
|
||||
interactive: bool,
|
||||
) -> Result<Context, Box<dyn Error>> {
|
||||
syncer.load_environment();
|
||||
|
||||
let mut context = Context::basic()?;
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||
let mut context = EvaluationContext::basic()?;
|
||||
|
||||
{
|
||||
use crate::commands::*;
|
||||
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(NuPlugin),
|
||||
// System/file operations
|
||||
whole_stream_command(Exec),
|
||||
whole_stream_command(Pwd),
|
||||
whole_stream_command(Ls),
|
||||
whole_stream_command(Du),
|
||||
@ -108,7 +95,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Kill),
|
||||
whole_stream_command(Version),
|
||||
whole_stream_command(Clear),
|
||||
whole_stream_command(What),
|
||||
whole_stream_command(Describe),
|
||||
whole_stream_command(Which),
|
||||
whole_stream_command(Debug),
|
||||
whole_stream_command(Alias),
|
||||
@ -127,6 +114,8 @@ pub fn create_default_context(
|
||||
whole_stream_command(Shells),
|
||||
whole_stream_command(Enter),
|
||||
whole_stream_command(Exit),
|
||||
// Viz
|
||||
whole_stream_command(Chart),
|
||||
// Viewers
|
||||
whole_stream_command(Autoview),
|
||||
whole_stream_command(Table),
|
||||
@ -136,7 +125,6 @@ pub fn create_default_context(
|
||||
whole_stream_command(SplitRow),
|
||||
whole_stream_command(SplitChars),
|
||||
whole_stream_command(Lines),
|
||||
whole_stream_command(Trim),
|
||||
whole_stream_command(Echo),
|
||||
whole_stream_command(Parse),
|
||||
whole_stream_command(Str),
|
||||
@ -159,7 +147,9 @@ pub fn create_default_context(
|
||||
whole_stream_command(StrEndsWith),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(StrLength),
|
||||
whole_stream_command(StrLPad),
|
||||
whole_stream_command(StrReverse),
|
||||
whole_stream_command(StrRPad),
|
||||
whole_stream_command(StrCamelCase),
|
||||
whole_stream_command(StrPascalCase),
|
||||
whole_stream_command(StrKebabCase),
|
||||
@ -169,7 +159,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(MoveColumn),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
@ -190,6 +180,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Nth),
|
||||
whole_stream_command(Drop),
|
||||
whole_stream_command(Format),
|
||||
whole_stream_command(FileSize),
|
||||
whole_stream_command(Where),
|
||||
whole_stream_command(If),
|
||||
whole_stream_command(Compact),
|
||||
@ -204,8 +195,11 @@ pub fn create_default_context(
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(IsEmpty),
|
||||
whole_stream_command(EachGroup),
|
||||
whole_stream_command(EachWindow),
|
||||
whole_stream_command(Empty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Flatten),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Merge),
|
||||
whole_stream_command(Shuffle),
|
||||
@ -229,6 +223,9 @@ pub fn create_default_context(
|
||||
whole_stream_command(MathSummation),
|
||||
whole_stream_command(MathVariance),
|
||||
whole_stream_command(MathProduct),
|
||||
whole_stream_command(MathRound),
|
||||
whole_stream_command(MathFloor),
|
||||
whole_stream_command(MathCeil),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
@ -265,6 +262,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(RandomDice),
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
whole_stream_command(RandomUUID),
|
||||
whole_stream_command(RandomInteger),
|
||||
// Path
|
||||
whole_stream_command(PathBasename),
|
||||
whole_stream_command(PathCommand),
|
||||
@ -280,9 +278,10 @@ pub fn create_default_context(
|
||||
whole_stream_command(UrlPath),
|
||||
whole_stream_command(UrlHost),
|
||||
whole_stream_command(UrlQuery),
|
||||
whole_stream_command(Seq),
|
||||
]);
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
{
|
||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||
}
|
||||
@ -295,28 +294,297 @@ pub async fn run_vec_of_pipelines(
|
||||
pipelines: Vec<String>,
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = crate::EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(&mut syncer, false)?;
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(false)?;
|
||||
let config = syncer.get_config();
|
||||
|
||||
let _ = register_plugins(&mut context);
|
||||
context.configure(&config, |_, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = register_plugins(ctx);
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
|
||||
for pipeline in pipelines {
|
||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let configuration = syncer.get_config();
|
||||
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
|
||||
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||
rl.set_helper(helper);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let skip_welcome_message = configuration
|
||||
.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
clap::crate_version!()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
loop {
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = configuration.var("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let prompt_block = nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
Scope::from_env(env),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".to_string()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
use crate::git::current_branch;
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let prompt = {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(&colored_prompt) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
"> ".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(s) => process_line(&s, &mut context, false, true).await,
|
||||
x => x,
|
||||
};
|
||||
|
||||
// Check the config to see if we need to update the path
|
||||
// TODO: make sure config is cached so we don't path this load every call
|
||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
if syncer.did_config_change() {
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
}
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
});
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = config::config(Tag::unknown())?
|
||||
.get("ctrlc_exit")
|
||||
.map(|s| s.value.is_true())
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
let _ = rl.save_history(&history_path);
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
ctrlcbreak = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::CtrlD => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctrlcbreak = false;
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = context.ctrl_c.clone();
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
})?;
|
||||
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_startup_commands(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(commands) = config.var("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
@ -324,36 +592,29 @@ pub async fn run_vec_of_pipelines(
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
let _ =
|
||||
run_pipeline_standalone(pipeline_string, false, context, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"expected a table of pipeline strings as startup commands",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pipeline in pipelines {
|
||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
||||
let line = process_line(&pipeline, context, redirect_stdin, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
@ -391,7 +652,8 @@ pub async fn run_pipeline_standalone(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
@ -410,6 +672,9 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
@ -428,23 +693,20 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
let config = match config::config(Tag::unknown()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Config could not be loaded.");
|
||||
if let ShellError {
|
||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
||||
..
|
||||
} = e
|
||||
{
|
||||
eprintln!("{}", diagnostic.message);
|
||||
}
|
||||
IndexMap::new()
|
||||
}
|
||||
};
|
||||
rl
|
||||
}
|
||||
|
||||
if let Ok(config) = config::config(Tag::unknown()) {
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
@ -500,9 +762,7 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => {
|
||||
rustyline::config::EditMode::Emacs
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
@ -521,9 +781,7 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => {
|
||||
rustyline::config::BellStyle::None
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
@ -535,9 +793,7 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => {
|
||||
rustyline::ColorMode::Disabled
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
@ -551,246 +807,29 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(rl, config)
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
pub async fn cli(
|
||||
mut syncer: EnvironmentSyncer,
|
||||
mut context: Context,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let configuration = nu_data::config::NuConfig::new();
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
|
||||
let _ = register_plugins(&mut context);
|
||||
|
||||
let (mut rl, config) = create_rustyline_configuration();
|
||||
|
||||
// we are ok if history does not exist
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let skip_welcome_message = config
|
||||
.get("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
clap::crate_version!()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
}
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
let hinter = init_hinter(&config);
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = config.get("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let mut prompt_block =
|
||||
nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
prompt_block.block.expand_it_usage();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".to_string()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let prompt = {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(&colored_prompt) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
"> ".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
let line = process_line(readline, &mut context, false, true).await;
|
||||
|
||||
// Check the config to see if we need to update the path
|
||||
// TODO: make sure config is cached so we don't path this load every call
|
||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = config::config(Tag::unknown())?
|
||||
.get("ctrlc_exit")
|
||||
.map(|s| s.value.expect_string() == "true")
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
let _ = rl.save_history(&history_path);
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
ctrlcbreak = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctrlcbreak = false;
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
||||
// Show hints unless explicitly disabled in config
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn nu_line_editor_helper(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
@ -806,11 +845,12 @@ fn chomp_newline(s: &str) -> &str {
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
CtrlC,
|
||||
Break,
|
||||
CtrlC,
|
||||
CtrlD,
|
||||
}
|
||||
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, ShellError> {
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||
let line = if line.ends_with('\n') {
|
||||
&line[..line.len() - 1]
|
||||
} else {
|
||||
@ -820,8 +860,7 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
|
||||
let lite_result = nu_parser::lite_parse(&line, 0)?;
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
classified_block.block.expand_it_usage();
|
||||
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
@ -830,9 +869,7 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
|
||||
&classified_block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
Scope::from_env(env),
|
||||
)
|
||||
.await?
|
||||
.collect_string(Tag::unknown())
|
||||
@ -842,15 +879,14 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
|
||||
|
||||
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||
pub async fn process_line(
|
||||
readline: Result<String, ReadlineError>,
|
||||
ctx: &mut Context,
|
||||
line: &str,
|
||||
ctx: &mut EvaluationContext,
|
||||
redirect_stdin: bool,
|
||||
cli_mode: bool,
|
||||
) -> LineResult {
|
||||
match &readline {
|
||||
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
|
||||
|
||||
Ok(line) => {
|
||||
if line.trim() == "" {
|
||||
LineResult::Success(line.to_string())
|
||||
} else {
|
||||
let line = chomp_newline(line);
|
||||
ctx.raw_input = line.to_string();
|
||||
|
||||
@ -865,7 +901,7 @@ pub async fn process_line(
|
||||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
let classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
|
||||
debug!("{:#?}", classified_block);
|
||||
//println!("{:#?}", pipeline);
|
||||
@ -982,17 +1018,13 @@ pub async fn process_line(
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
classified_block.block.expand_it_usage();
|
||||
|
||||
trace!("{:#?}", classified_block);
|
||||
let env = ctx.get_env();
|
||||
match run_block(
|
||||
&classified_block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
Scope::from_env(env),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -1038,13 +1070,6 @@ pub async fn process_line(
|
||||
Err(err) => LineResult::Error(line.to_string(), err),
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::Break,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
@ -1069,7 +1094,7 @@ mod tests {
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
||||
let context = crate::context::Context::basic().unwrap();
|
||||
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||
}
|
||||
true
|
||||
|
64
crates/nu-cli/src/command_registry.rs
Normal file
64
crates/nu-cli/src/command_registry.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -17,8 +17,9 @@ pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod chart;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod compact;
|
||||
@ -29,15 +30,19 @@ pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod describe;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod empty;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod flatten;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod from;
|
||||
pub(crate) mod from_csv;
|
||||
@ -64,7 +69,6 @@ pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
@ -75,6 +79,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;
|
||||
@ -93,6 +98,7 @@ pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod seq;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
@ -114,12 +120,10 @@ 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_;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
@ -133,7 +137,7 @@ pub(crate) use command::{
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use append::Command as Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||
@ -141,6 +145,7 @@ pub(crate) use benchmark::Benchmark;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use chart::Chart;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::{
|
||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
@ -150,14 +155,18 @@ pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use describe::Describe;
|
||||
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 empty::Command as Empty;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
@ -165,9 +174,11 @@ 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;
|
||||
pub(crate) use flatten::Command as Flatten;
|
||||
pub(crate) use format::{FileSize, Format};
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_csv::FromCSV;
|
||||
pub(crate) use from_eml::FromEML;
|
||||
@ -185,25 +196,25 @@ pub(crate) use from_xml::FromXML;
|
||||
pub(crate) use from_yaml::FromYAML;
|
||||
pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use get::Get;
|
||||
pub(crate) use group_by::GroupBy;
|
||||
pub(crate) use group_by::Command as GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use insert::Command as Insert;
|
||||
pub(crate) use into_int::IntoInt;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||
MathStddev, MathSummation, MathVariance,
|
||||
Math, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian, MathMinimum,
|
||||
MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use move_::{Move, MoveColumn, Mv};
|
||||
pub(crate) use move_::{Move, Mv};
|
||||
pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
@ -218,7 +229,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;
|
||||
@ -228,6 +239,7 @@ pub(crate) use rm::Remove;
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Select;
|
||||
pub(crate) use seq::Seq;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
@ -238,9 +250,9 @@ pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
|
||||
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||
StrReverse, StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring,
|
||||
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
@ -255,12 +267,53 @@ 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;
|
||||
pub(crate) use what::What;
|
||||
pub(crate) use where_::Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::commands::whole_stream_command;
|
||||
use crate::examples::{test_anchors, test_examples};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
fn full_tests() -> Vec<Command> {
|
||||
vec![
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Empty),
|
||||
]
|
||||
}
|
||||
|
||||
fn only_examples() -> Vec<Command> {
|
||||
let mut commands = full_tests();
|
||||
commands.extend(vec![whole_stream_command(Flatten)]);
|
||||
commands
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
for cmd in only_examples() {
|
||||
test_examples(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracks_metadata() -> Result<(), ShellError> {
|
||||
for cmd in full_tests() {
|
||||
test_anchors(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
|
||||
use deduction_to_signature::DeductionToSignature;
|
||||
use log::trace;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
@ -86,7 +86,6 @@ pub async fn alias(
|
||||
},
|
||||
_ctx,
|
||||
) = args.process(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||
@ -110,7 +109,7 @@ pub async fn alias(
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
// add to startup if alias doesn't exist and replace if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
@ -132,10 +131,18 @@ pub async fn alias(
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
let mut processed_args: Vec<VarDeclaration> = vec![];
|
||||
for (_, item) in list.iter().enumerate() {
|
||||
match item.as_string() {
|
||||
Ok(var_name) => {
|
||||
let dollar_var_name = format!("${}", var_name);
|
||||
processed_args.push(VarDeclaration {
|
||||
name: dollar_var_name,
|
||||
// type_decl: None,
|
||||
span: item.tag.span,
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
@ -143,208 +150,72 @@ pub async fn alias(
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Found vars: {:?}", processed_args);
|
||||
|
||||
let inferred_shapes = {
|
||||
if let Some(true) = infer {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
||||
block,
|
||||
),
|
||||
)))
|
||||
VarSyntaxShapeDeductor::infer_vars(&processed_args, &block, ®istry)?
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
processed_args
|
||||
.into_iter()
|
||||
.map(|arg| (arg, SyntaxShape::Any))
|
||||
.collect(),
|
||||
block,
|
||||
),
|
||||
)))
|
||||
processed_args.into_iter().map(|arg| (arg, None)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn to_arg_shapes(
|
||||
args: Vec<String>,
|
||||
block: &Block,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
|
||||
match find_block_shapes(block, registry) {
|
||||
Ok(found) => Ok(args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
(
|
||||
arg.clone(),
|
||||
match found.get(arg) {
|
||||
None | Some((_, None)) => SyntaxShape::Any,
|
||||
Some((_, Some(shape))) => *shape,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
|
||||
|
||||
fn check_insert(
|
||||
existing: &mut ShapeMap,
|
||||
to_add: (String, (Span, Option<SyntaxShape>)),
|
||||
) -> Result<(), ShellError> {
|
||||
match (to_add.1).1 {
|
||||
None => match existing.get(&to_add.0) {
|
||||
None => {
|
||||
existing.insert(to_add.0, to_add.1);
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Ok(()),
|
||||
},
|
||||
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
|
||||
None => Ok(()),
|
||||
Some(exist) => match exist.1 {
|
||||
None => Ok(()),
|
||||
Some(shape) => match shape {
|
||||
SyntaxShape::Any => Ok(()),
|
||||
shape if shape == new => Ok(()),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Type conflict in alias variable use",
|
||||
format!("{:?}", new),
|
||||
(to_add.1).0,
|
||||
format!("{:?}", shape),
|
||||
exist.0,
|
||||
)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
|
||||
for (k, v) in new.iter() {
|
||||
check_insert(existing, (k.clone(), *v))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_expr_shapes(
|
||||
spanned_expr: &SpannedExpression,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<ShapeMap, ShellError> {
|
||||
match &spanned_expr.expr {
|
||||
// TODO range will need similar if/when invocations can be parsed within range expression
|
||||
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
|
||||
find_expr_shapes(&bin.right, registry)
|
||||
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
|
||||
}),
|
||||
Expression::Block(b) => find_block_shapes(&b, registry),
|
||||
Expression::Path(path) => match &path.head.expr {
|
||||
Expression::Invocation(b) => find_block_shapes(&b, registry),
|
||||
Expression::Variable(Variable::Other(var, _)) => {
|
||||
let mut result = HashMap::new();
|
||||
result.insert(var.to_string(), (spanned_expr.span, None));
|
||||
Ok(result)
|
||||
}
|
||||
_ => Ok(HashMap::new()),
|
||||
},
|
||||
_ => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
|
||||
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
|
||||
found
|
||||
.iter()
|
||||
.map(|(v, sh)| match sh.1 {
|
||||
None => (v.clone(), (sh.0, Some(sig_shape))),
|
||||
Some(shape) => (v.clone(), (sh.0, Some(shape))),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let signature = DeductionToSignature::get(&name.item, &inferred_shapes);
|
||||
trace!("Inferred signature: {:?}", signature);
|
||||
|
||||
let mut arg_shapes = HashMap::new();
|
||||
for pipeline in &block.block {
|
||||
for classified in &pipeline.list {
|
||||
match classified {
|
||||
ClassifiedCommand::Expr(spanned_expr) => {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
check_merge(&mut arg_shapes, &found)?
|
||||
}
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
if let Some(signature) = registry.get(&internal.name) {
|
||||
if let Some(positional) = &internal.args.positional {
|
||||
for (i, spanned_expr) in positional.iter().enumerate() {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
if i >= signature.positional.len() {
|
||||
if let Some((sig_shape, _)) = &signature.rest_positional {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
} else {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
} else {
|
||||
let (pos_type, _) = &signature.positional[i];
|
||||
match pos_type {
|
||||
// TODO pass on mandatory/optional?
|
||||
PositionalType::Mandatory(_, sig_shape)
|
||||
| PositionalType::Optional(_, sig_shape) => {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, val) in named.iter() {
|
||||
if let NamedValue::Value(_, spanned_expr) = val {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
match signature.named.get(name) {
|
||||
None => {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
Some((named_type, _)) => {
|
||||
if let NamedType::Mandatory(_, sig_shape)
|
||||
| NamedType::Optional(_, sig_shape) = named_type
|
||||
{
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!("registry has lost name it provided");
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arg_shapes)
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(Box::new(signature), block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
Ok(test_examples(Alias {})?)
|
||||
}
|
||||
}
|
||||
|
||||
mod deduction_to_signature {
|
||||
//For now this logic is relativly simple.
|
||||
//For each var, one mandatory positional is added.
|
||||
//As soon as more support for optional positional arguments is arrived,
|
||||
//this logic might be a little bit more tricky.
|
||||
use crate::types::deduction::{Deduction, VarDeclaration};
|
||||
use nu_protocol::{PositionalType, Signature, SyntaxShape};
|
||||
|
||||
pub struct DeductionToSignature {}
|
||||
impl DeductionToSignature {
|
||||
pub fn get(
|
||||
cmd_name: &str,
|
||||
deductions: &[(VarDeclaration, Option<Deduction>)],
|
||||
) -> Signature {
|
||||
let mut signature = Signature::build(cmd_name);
|
||||
for (decl, deduction) in deductions {
|
||||
match deduction {
|
||||
None => signature.positional.push((
|
||||
PositionalType::optional(&decl.name, SyntaxShape::Any),
|
||||
decl.name.clone(),
|
||||
)),
|
||||
Some(deduction) => match deduction {
|
||||
Deduction::VarShapeDeduction(normal_var_deduction) => {
|
||||
signature.positional.push((
|
||||
PositionalType::optional(
|
||||
&decl.name,
|
||||
normal_var_deduction[0].deduction,
|
||||
),
|
||||
decl.name.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,15 @@ use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
escape: Option<Tagged<String>>,
|
||||
osc: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
Signature::build("ansi")
|
||||
.optional(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
.named(
|
||||
"escape", // \x1b
|
||||
SyntaxShape::Any,
|
||||
"escape sequence without the escape character(s)",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"osc",
|
||||
SyntaxShape::Any,
|
||||
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
r#"Output ANSI codes to change color
|
||||
|
||||
For escape sequences:
|
||||
Escape: '\x1b[' is not required for --escape parameter
|
||||
Format: #(;#)m
|
||||
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
|
||||
There can be multiple text formatting sequence numbers
|
||||
separated by a ; and ending with an m where the # is of the
|
||||
following values:
|
||||
attributes
|
||||
0 reset / normal display
|
||||
1 bold or increased intensity
|
||||
2 faint or decreased intensity
|
||||
3 italic on (non-mono font)
|
||||
4 underline on
|
||||
5 slow blink on
|
||||
6 fast blink on
|
||||
7 reverse video on
|
||||
8 nondisplayed (invisible) on
|
||||
9 strike-through on
|
||||
|
||||
foreground/bright colors background/bright colors
|
||||
30/90 black 40/100 black
|
||||
31/91 red 41/101 red
|
||||
32/92 green 42/102 green
|
||||
33/93 yellow 43/103 yellow
|
||||
34/94 blue 44/104 blue
|
||||
35/95 magenta 45/105 magenta
|
||||
36/96 cyan 46/106 cyan
|
||||
37/97 white 47/107 white
|
||||
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
OSC: '\x1b]' is not required for --osc parameter
|
||||
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
|
||||
Format: #
|
||||
0 Set window title and icon name
|
||||
1 Set icon name
|
||||
2 Set window title
|
||||
4 Set/read color palette
|
||||
9 iTerm2 Grown notifications
|
||||
10 Set foreground color (x11 color spec)
|
||||
11 Set background color (x11 color spec)
|
||||
... others"#
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -49,6 +107,14 @@ impl WholeStreamCommand for Ansi {
|
||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -57,10 +123,41 @@ impl WholeStreamCommand for Ansi {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
let (AnsiArgs { color, escape, osc }, _) = args.process(®istry).await?;
|
||||
|
||||
if let Some(e) = escape {
|
||||
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||
if esc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
e.tag(),
|
||||
));
|
||||
}
|
||||
let output = format!("\x1b[{}", e.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(e.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(o) = osc {
|
||||
let osc_vec: Vec<char> = o.item.chars().collect();
|
||||
if osc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
o.tag(),
|
||||
));
|
||||
}
|
||||
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
|
||||
// OCS's need to end with a bell '\x07' char
|
||||
let output = format!("\x1b]{};", o.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(o.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,11 +233,12 @@ pub fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
Ok(test_examples(Ansi {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
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};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AppendArgs {
|
||||
row: Value,
|
||||
struct Arguments {
|
||||
value: Value,
|
||||
}
|
||||
|
||||
pub struct Append;
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Append {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
}
|
||||
@ -26,7 +26,7 @@ impl WholeStreamCommand for Append {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append the given row to the table"
|
||||
"Append a row to the table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -34,16 +34,40 @@ impl WholeStreamCommand for Append {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
||||
let (Arguments { mut value }, input) = args.process(registry).await?;
|
||||
|
||||
let eos = futures::stream::iter(vec![row]);
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
Ok(input.chain(eos).to_output_stream())
|
||||
if let Some(first) = input.get(0) {
|
||||
value.tag = first.tag();
|
||||
}
|
||||
|
||||
// Checks if we are trying to append a row literal
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(values),
|
||||
tag,
|
||||
} = &value
|
||||
{
|
||||
if values.len() == 1 && values[0].is_row() {
|
||||
value = values[0].value.clone().into_value(tag);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
input
|
||||
.into_iter()
|
||||
.chain(vec![value])
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Add something to the end of a list or table",
|
||||
use nu_protocol::row;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Add values to the end of the table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
@ -51,18 +75,16 @@ impl WholeStreamCommand for Append {
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Append;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Append {})
|
||||
},
|
||||
Example {
|
||||
description: "Add row value to the end of the table",
|
||||
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||
result: Some(vec![
|
||||
row! { "country".into() => Value::from("Ecuador")},
|
||||
row! { "country".into() => Value::from("New Zealand")},
|
||||
row! { "country".into() => Value::from("USA")},
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::new(),
|
||||
scope: Scope::create(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -326,11 +326,12 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
||||
|
@ -10,25 +10,16 @@ pub enum AutoPivotMode {
|
||||
|
||||
impl AutoPivotMode {
|
||||
pub fn is_auto(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Auto => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Auto)
|
||||
}
|
||||
|
||||
pub fn is_always(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Always => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Always)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn is_never(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Never => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Never)
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +28,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() {
|
||||
|
@ -1,18 +1,26 @@
|
||||
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 rand::{
|
||||
distributions::Alphanumeric,
|
||||
prelude::{thread_rng, Rng},
|
||||
};
|
||||
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 +30,22 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("benchmark").required(
|
||||
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 +55,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,31 +78,135 @@ async fn benchmark(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let tag = raw_args.call_info.args.span;
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block }, input) = raw_args.process(®istry).await?;
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
let start_time: chrono::DateTime<_> = Utc::now();
|
||||
let env = scope.env();
|
||||
let name = generate_free_name(&env);
|
||||
let mut env = IndexMap::new();
|
||||
env.insert(name, generate_random_env_value());
|
||||
let scope = Scope::append_env(scope, env);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let start_time = Instant::now();
|
||||
|
||||
let _ = result?.drain_vec().await;
|
||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let start = time().await;
|
||||
|
||||
let result = run_block(&block, &mut context, input, scope.clone()).await;
|
||||
let output = result?.into_vec().await;
|
||||
|
||||
#[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: Arc<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).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()
|
||||
}
|
||||
|
||||
fn generate_random_env_value() -> String {
|
||||
let mut thread_rng = thread_rng();
|
||||
let len = thread_rng.gen_range(1, 16 * 1024);
|
||||
thread_rng.sample_iter(&Alphanumeric).take(len).collect()
|
||||
}
|
||||
|
||||
fn generate_free_name(env: &indexmap::IndexMap<String, String>) -> String {
|
||||
let mut thread_rng = thread_rng();
|
||||
loop {
|
||||
let candidate_name = format!("NU_RANDOM_VALUE_{}", thread_rng.gen::<usize>());
|
||||
if !env.contains_key(&candidate_name) {
|
||||
return candidate_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,11 +339,12 @@ fn add_month_to_table(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cal;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cal {})
|
||||
Ok(test_examples(Cal {})?)
|
||||
}
|
||||
}
|
||||
|
@ -72,11 +72,12 @@ impl WholeStreamCommand for Cd {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cd;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cd {})
|
||||
Ok(test_examples(Cd {})?)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ pub struct Char;
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
unicode: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
Signature::build("ansi")
|
||||
.required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
|
||||
UntaggedValue::string("\u{2261}").into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Output unicode character",
|
||||
example: r#"char -u 1f378"#,
|
||||
result: Some(vec![Value::from("\u{1f378}")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -53,22 +61,42 @@ impl WholeStreamCommand for Char {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||
let (CharArgs { name, unicode }, _) = args.process(®istry).await?;
|
||||
|
||||
if unicode {
|
||||
let decoded_char = string_to_unicode_char(&name.item);
|
||||
if let Some(output) = decoded_char {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding unicode character",
|
||||
"error decoding unicode character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let special_character = str_to_character(&name.item);
|
||||
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown character",
|
||||
"unknown character",
|
||||
"error finding named character",
|
||||
"error finding named character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||
u32::from_str_radix(s, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32)
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"sp" | "space" => Some(" ".into()),
|
||||
// Unicode names came from https://www.compart.com/en/unicode
|
||||
// Private Use Area (U+E000-U+F8FF)
|
||||
// Unicode can't be mixed with Ansi or it will break width calculation
|
||||
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||
|
||||
@ -94,6 +123,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" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||
"moon" => Some("🌛".to_string()),
|
||||
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||
"snowy" | "snow" => Some("❄️".to_string()),
|
||||
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
@ -101,11 +159,12 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Char {})
|
||||
Ok(test_examples(Char {})?)
|
||||
}
|
||||
}
|
||||
|
53
crates/nu-cli/src/commands/chart.rs
Normal file
53
crates/nu-cli/src/commands/chart.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chart;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Chart {
|
||||
fn name(&self) -> &str {
|
||||
"chart"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("chart")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Displays charts."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if registry.get_command("chart bar").is_none() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"nu_plugin_chart not installed.",
|
||||
));
|
||||
}
|
||||
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Chart, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Chart;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Chart {})?)
|
||||
}
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
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;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
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>,
|
||||
env: &IndexMap<String, String>,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for pipeline in &block.block {
|
||||
@ -54,7 +52,7 @@ pub(crate) async fn run_block(
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
||||
output = run_pipeline(pipeline, ctx, input, scope.clone()).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
@ -64,11 +62,9 @@ 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>,
|
||||
env: &IndexMap<String, String>,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
for item in commands.list.clone() {
|
||||
input = match item {
|
||||
@ -77,13 +73,13 @@ async fn run_pipeline(
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => {
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
run_expression_block(*expr, ctx, scope.clone()).await?
|
||||
}
|
||||
|
||||
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||
|
||||
ClassifiedCommand::Internal(left) => {
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
run_internal_command(left, ctx, input, scope.clone()).await?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,14 +6,12 @@ use log::{log_enabled, trace};
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
use nu_protocol::Value;
|
||||
use nu_protocol::Scope;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
context: &mut EvaluationContext,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
@ -21,7 +19,7 @@ pub(crate) async fn run_expression_block(
|
||||
}
|
||||
|
||||
let registry = context.registry().clone();
|
||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
||||
let output = evaluate_baseline_expr(&expr, ®istry, scope).await?;
|
||||
|
||||
Ok(once(async { Ok(output) }).to_input_stream())
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::futures::ThreadedReceiver;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
@ -13,15 +14,16 @@ use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
scope: Arc<Scope>,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
@ -39,9 +41,9 @@ pub(crate) async fn run_external_command(
|
||||
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
scope: Arc<Scope>,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
@ -50,9 +52,8 @@ async fn run_with_stdin(
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let value =
|
||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
||||
.await?;
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
@ -67,8 +68,10 @@ async fn run_with_stdin(
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args
|
||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
||||
command_args.push((
|
||||
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||
is_literal,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
@ -82,14 +85,14 @@ async fn run_with_stdin(
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
command_args.push(trimmed_value_string);
|
||||
command_args.push((trimmed_value_string, is_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
.map(|(arg, _is_literal)| {
|
||||
let home_dir;
|
||||
|
||||
#[cfg(feature = "dirs")]
|
||||
@ -105,8 +108,9 @@ async fn run_with_stdin(
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
||||
add_quotes(&arg)
|
||||
if !_is_literal {
|
||||
let escaped = escape_double_quotes(&arg);
|
||||
add_double_quotes(&escaped)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
@ -138,7 +142,7 @@ fn spawn(
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
scope: &Scope,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
@ -169,7 +173,7 @@ fn spawn(
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.env.iter());
|
||||
process.envs(scope.env());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
@ -221,36 +225,19 @@ fn spawn(
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
if stdin_write.write(s.as_bytes()).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if let Err(e) = stdin_write.write(b) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
if stdin_write.write(b).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
println!("Unsupported: {:?}", unsupported);
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
@ -496,11 +483,6 @@ where
|
||||
shellexpand::tilde_with_context(input, home_dir)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
||||
argument.chars().any(|c| c.is_whitespace())
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
@ -511,10 +493,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_quotes(argument: &str) -> String {
|
||||
fn add_double_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||
// allocate new string only if required
|
||||
if argument.contains('"') {
|
||||
Cow::Owned(argument.replace('"', r#"\""#))
|
||||
} else {
|
||||
Cow::Borrowed(argument)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
@ -540,10 +532,10 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
add_double_quotes, argument_is_quoted, escape_double_quotes, 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,13 +565,14 @@ 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,
|
||||
&mut ctx,
|
||||
input,
|
||||
&Scope::new(),
|
||||
Scope::create(),
|
||||
ExternalRedirection::Stdout
|
||||
)
|
||||
.await
|
||||
@ -591,7 +584,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.");
|
||||
@ -619,10 +612,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
||||
fn checks_escape_double_quotes() {
|
||||
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -650,9 +643,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
||||
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -9,22 +9,15 @@ 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>,
|
||||
env: &IndexMap<String, String>,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
}
|
||||
|
||||
let scope = Scope {
|
||||
it: it.clone(),
|
||||
vars: vars.clone(),
|
||||
env: env.clone(),
|
||||
};
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
@ -38,7 +31,7 @@ pub(crate) async fn run_internal_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&scope,
|
||||
scope.clone(),
|
||||
objects,
|
||||
)
|
||||
.await?
|
||||
@ -48,8 +41,6 @@ pub(crate) async fn run_internal_command(
|
||||
//let context = Arc::new(context.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
let scope = Arc::new(scope);
|
||||
// let scope = scope.clone();
|
||||
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
@ -90,7 +81,7 @@ pub(crate) async fn run_internal_command(
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
scope,
|
||||
},
|
||||
};
|
||||
let result = converter
|
||||
@ -194,12 +185,34 @@ pub(crate) async fn run_internal_command(
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
CommandAction::AddAlias(sig, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
AliasCommand::new(*sig, block),
|
||||
)]);
|
||||
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![]))
|
||||
|
@ -130,7 +130,7 @@ async fn run_filter(
|
||||
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
||||
]);
|
||||
|
||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
let args = args.evaluate_once_with_scope(®istry, scope).await?;
|
||||
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
|
@ -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> {
|
||||
@ -43,15 +43,3 @@ impl WholeStreamCommand for Clear {
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clear;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clear {})
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -32,11 +32,18 @@ impl WholeStreamCommand for Clip {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
vec![
|
||||
Example {
|
||||
description: "Save text to the clipboard",
|
||||
example: "echo 'secret value' | clip",
|
||||
result: None,
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Save numbers to the clipboard",
|
||||
example: "random integer 10000000..99999999 | clip",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,16 +68,14 @@ pub async fn clip(
|
||||
first = false;
|
||||
}
|
||||
|
||||
let string: String = match i.as_string() {
|
||||
Ok(string) => string.to_string(),
|
||||
Err(_) => {
|
||||
let string: String = i.convert_to_string();
|
||||
if string.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Given non-string data",
|
||||
"expected strings from pipeline",
|
||||
"Unable to convert to string",
|
||||
"Unable to convert to string",
|
||||
name,
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
new_copy_data.push_str(&string);
|
||||
}
|
||||
@ -99,11 +104,12 @@ pub async fn clip(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clip;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clip {})
|
||||
Ok(test_examples(Clip {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
@ -17,27 +17,12 @@ use std::sync::atomic::AtomicBool;
|
||||
pub struct UnevaluatedCallInfo {
|
||||
pub args: hir::Call,
|
||||
pub name_tag: Tag,
|
||||
pub scope: Scope,
|
||||
pub scope: Arc<Scope>,
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn evaluate_with_new_it(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
) -> Result<CallInfo, ShellError> {
|
||||
let mut scope = self.scope.clone();
|
||||
scope.it = it.clone();
|
||||
let args = evaluate_args(&self.args, registry, &scope).await?;
|
||||
let args = evaluate_args(&self.args, registry, self.scope.clone()).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
@ -115,7 +100,7 @@ impl CommandArgs {
|
||||
pub async fn evaluate_once_with_scope(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
scope: Arc<Scope>,
|
||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
@ -215,37 +200,6 @@ impl EvaluatedWholeStreamCommandArgs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub"]
|
||||
pub struct EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedFilterCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedFilterCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
) -> EvaluatedFilterCommandArgs {
|
||||
EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct EvaluatedCommandArgs {
|
||||
@ -303,6 +257,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,74 +326,15 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnFilterCommand {
|
||||
name: String,
|
||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FnFilterCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"usage"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
CommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
..
|
||||
}: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let func = self.func;
|
||||
|
||||
Ok(input
|
||||
.then(move |it| {
|
||||
let host = host.clone();
|
||||
let registry = registry.clone();
|
||||
let ctrl_c = ctrl_c.clone();
|
||||
let shell_manager = shell_manager.clone();
|
||||
let call_info = call_info.clone();
|
||||
async move {
|
||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(err));
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => return OutputStream::one(Err(err)),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
||||
|
@ -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;
|
||||
@ -37,22 +37,11 @@ impl WholeStreamCommand for Compact {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter out all null entries in a list",
|
||||
example: "echo [1 2 $null 3 $null $null] | compact",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
vec![Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -la | compact target",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,11 +84,12 @@ pub async fn compact(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Compact;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Compact {})
|
||||
Ok(test_examples(Compact {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -1,15 +1,14 @@
|
||||
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};
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
get: Tagged<String>,
|
||||
path: ColumnPath,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -21,7 +20,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config get").required(
|
||||
"get",
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::ColumnPath,
|
||||
"value to get from the config",
|
||||
)
|
||||
}
|
||||
@ -51,17 +50,14 @@ pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (GetArgs { get }, _) = args.process(®istry).await?;
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (GetArgs { path }, _) = args.process(®istry).await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
.get(&key)
|
||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", get.tag()))?;
|
||||
let value = crate::commands::get::get_column_path(&path, &result)?;
|
||||
|
||||
Ok(match value {
|
||||
Value {
|
||||
@ -75,9 +71,6 @@ pub async fn get(
|
||||
|
||||
futures::stream::iter(list).to_output_stream()
|
||||
}
|
||||
x => {
|
||||
let x = x.clone();
|
||||
OutputStream::one(ReturnSuccess::value(x))
|
||||
}
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
})
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -1,15 +1,14 @@
|
||||
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};
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
key: Tagged<String>,
|
||||
path: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
@ -21,7 +20,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set")
|
||||
.required("key", SyntaxShape::String, "variable name to set")
|
||||
.required("key", SyntaxShape::ColumnPath, "variable name to set")
|
||||
.required("value", SyntaxShape::Any, "value to use")
|
||||
}
|
||||
|
||||
@ -38,11 +37,28 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set nonzero_exit_errors to true",
|
||||
example: "config set nonzero_exit_errors $true",
|
||||
vec![
|
||||
Example {
|
||||
description: "Set auto pivoting",
|
||||
example: "config set pivot_mode always",
|
||||
result: None,
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Set line editor options",
|
||||
example: "config set line_editor [[edit_mode, completion_type]; [emacs circular]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set coloring options",
|
||||
example: "config set color_config [[header_align header_bold]; [left $true]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set nested options",
|
||||
example: "config set color_config.header_color white",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,18 +66,32 @@ pub async fn set(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (SetArgs { key, value }, _) = args.process(®istry).await?;
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (SetArgs { path, mut value }, _) = args.process(®istry).await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
let raw_entries = nu_data::config::read(&name_tag, &None)?;
|
||||
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
if let UntaggedValue::Table(rows) = &value.value {
|
||||
if rows.len() == 1 && rows[0].is_row() {
|
||||
value = rows[0].clone();
|
||||
}
|
||||
}
|
||||
|
||||
config::write(&result, &None)?;
|
||||
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Row(changes),
|
||||
..
|
||||
}) => {
|
||||
config::write(&changes.entries, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
||||
UntaggedValue::Row(changes).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
@ -80,11 +80,12 @@ impl WholeStreamCommand for Count {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Count {})
|
||||
Ok(test_examples(Count {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -66,11 +66,12 @@ impl WholeStreamCommand for Cpy {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cpy;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cpy {})
|
||||
Ok(test_examples(Cpy {})?)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Work with dates."
|
||||
"Apply date function"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -36,11 +36,12 @@ impl WholeStreamCommand for Command {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
||||
|
@ -61,3 +61,16 @@ pub async fn format(
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
@ -48,3 +48,16 @@ pub async fn now(
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
@ -48,3 +48,16 @@ pub async fn utc(
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
@ -55,11 +55,12 @@ async fn debug_value(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Debug;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Debug {})
|
||||
Ok(test_examples(Debug {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -83,11 +83,12 @@ async fn default(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Default;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Default {})
|
||||
Ok(test_examples(Default {})?)
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct What;
|
||||
pub struct Describe;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WhatArgs {}
|
||||
pub struct DescribeArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for What {
|
||||
impl WholeStreamCommand for Describe {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
@ -28,11 +28,11 @@ impl WholeStreamCommand for What {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
what(args, registry).await
|
||||
describe(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn what(
|
||||
pub async fn describe(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
@ -49,12 +49,13 @@ pub async fn what(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::What;
|
||||
use super::Describe;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(What {})
|
||||
Ok(test_examples(Describe {})?)
|
||||
}
|
||||
}
|
@ -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, ®istry);
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
@ -95,15 +95,7 @@ async fn do_(
|
||||
|
||||
block.set_redirect(block_redirection);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let result = run_block(&block, &mut context, input, scope).await;
|
||||
|
||||
if ignore_errors {
|
||||
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
||||
@ -125,11 +117,12 @@ async fn do_(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Do;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Do {})
|
||||
Ok(test_examples(Do {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -85,11 +85,12 @@ async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Drop;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Drop {})
|
||||
Ok(test_examples(Drop {})?)
|
||||
}
|
||||
}
|
||||
|
@ -427,11 +427,12 @@ impl From<FileInfo> for Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Du;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Du {})
|
||||
Ok(test_examples(Du {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
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;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
||||
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
@ -73,36 +72,35 @@ impl WholeStreamCommand for Each {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||
matches!(&*head, SpannedExpression {
|
||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||
..
|
||||
} if s == "expanded-each")
|
||||
}
|
||||
|
||||
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();
|
||||
let input_stream = if is_expanded_it_usage(&head) {
|
||||
// When we process a row, we need to know whether the block wants to have the contents of the row as
|
||||
// a parameter to the block (so it gets assigned to a variable that can be used inside the block) or
|
||||
// if it wants the contents as as an input stream
|
||||
|
||||
let input_stream = if !block.params.is_empty() {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
};
|
||||
Ok(run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
|
||||
let scope = if !block.params.is_empty() {
|
||||
// FIXME: add check for more than parameter, once that's supported
|
||||
Scope::append_var(scope, block.params[0].clone(), input)
|
||||
} else {
|
||||
scope
|
||||
};
|
||||
|
||||
Ok(
|
||||
run_block(&block, Arc::make_mut(&mut context), input_stream, scope)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
.to_output_stream(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
@ -118,9 +116,8 @@ async fn each(
|
||||
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(Context::from_raw(&raw_args, ®istry));
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
|
||||
@ -130,12 +127,11 @@ async fn each(
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
let row = make_indexed_item(input.0, input.1);
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, row).await {
|
||||
match process_row(block, scope, context, row).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
@ -148,11 +144,10 @@ async fn each(
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, input).await {
|
||||
match process_row(block, scope, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
@ -166,11 +161,12 @@ async fn each(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Each;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Each {})
|
||||
Ok(test_examples(Each {})?)
|
||||
}
|
||||
}
|
123
crates/nu-cli/src/commands/each/group.rs
Normal file
123
crates/nu-cli/src/commands/each/group.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::commands::each::process_row;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, 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 scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process(®istry).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(), context.clone())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
let value = Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag: Tag::unknown(),
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, scope, 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;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(EachGroup {})?)
|
||||
}
|
||||
}
|
9
crates/nu-cli/src/commands/each/mod.rs
Normal file
9
crates/nu-cli/src/commands/each/mod.rs
Normal 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;
|
112
crates/nu-cli/src/commands/each/window.rs
Normal file
112
crates/nu-cli/src/commands/each/window.rs
Normal file
@ -0,0 +1,112 @@
|
||||
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 scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(®istry).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 context = context.clone();
|
||||
let local_window = window.clone();
|
||||
|
||||
async move {
|
||||
if i % stride == 0 {
|
||||
Some(run_block_on_vec(local_window, block, scope, context).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|x| async { x })
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachWindow;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(EachWindow {})?)
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -138,11 +162,12 @@ impl Iterator for RangeIterator {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Echo {})
|
||||
Ok(test_examples(Echo {})?)
|
||||
}
|
||||
}
|
||||
|
288
crates/nu-cli/src/commands/empty.rs
Normal file
288
crates/nu-cli/src/commands/empty.rs
Normal file
@ -0,0 +1,288 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::{as_string, ValueExt};
|
||||
|
||||
use futures::stream::once;
|
||||
use indexmap::indexmap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness. Pass an optional block to replace if empty",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check for empty values"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Check if a value is empty",
|
||||
example: "echo '' | empty?",
|
||||
result: Some(vec![UntaggedValue::boolean(true).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "more than one column",
|
||||
example: "echo [[meal size]; [arepa small] [taco '']] | empty? meal size",
|
||||
result: Some(
|
||||
vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"meal".to_string() => Value::from(false),
|
||||
"size".to_string() => Value::from(false),
|
||||
})
|
||||
.into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"meal".to_string() => Value::from(false),
|
||||
"size".to_string() => Value::from(true),
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
),
|
||||
},Example {
|
||||
description: "use a block if setting the empty cell contents is wanted",
|
||||
example: "echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] }",
|
||||
result: Some(
|
||||
vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"2020/04/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(33).into(), UntaggedValue::int(37).into()]).into(),
|
||||
"2020/07/10".to_string() => UntaggedValue::table(&[UntaggedValue::int(27).into()]).into(),
|
||||
"2020/11/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(37).into()]).into(),
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let name_tag = Arc::new(args.call_info.name_tag.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let scope = args.call_info.scope.clone();
|
||||
let (Arguments { rest }, input) = args.process(®istry).await?;
|
||||
let (columns, default_block): (Vec<ColumnPath>, Option<Block>) = arguments(rest)?;
|
||||
let default_block = Arc::new(default_block);
|
||||
|
||||
if input.is_empty() {
|
||||
let stream = futures::stream::iter(vec![
|
||||
UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)
|
||||
]);
|
||||
|
||||
return Ok(InputStream::from_stream(stream)
|
||||
.then(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = vec![];
|
||||
|
||||
async {
|
||||
match process_row(scope, context, input, block, columns, tag).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream());
|
||||
}
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = columns.clone();
|
||||
|
||||
async {
|
||||
match process_row(scope, context, input, block, columns, tag).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn arguments(rest: Vec<Value>) -> Result<(Vec<ColumnPath>, Option<Block>), ShellError> {
|
||||
let mut rest = rest;
|
||||
let mut columns = vec![];
|
||||
let mut default = None;
|
||||
|
||||
let last_argument = rest.pop();
|
||||
|
||||
match last_argument {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Block(call),
|
||||
..
|
||||
}) => default = Some(call),
|
||||
Some(other) => {
|
||||
let Tagged { item: path, .. } = other.as_column_path()?;
|
||||
|
||||
columns = vec![path];
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
for argument in rest {
|
||||
let Tagged { item: path, .. } = argument.as_column_path()?;
|
||||
|
||||
columns.push(path);
|
||||
}
|
||||
|
||||
Ok((columns, default))
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
default_block: Arc<Option<Block>>,
|
||||
column_paths: Vec<ColumnPath>,
|
||||
tag: Arc<Tag>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let _tag = &*tag;
|
||||
let mut out = Arc::new(None);
|
||||
let results = Arc::make_mut(&mut out);
|
||||
|
||||
if let Some(default_block) = &*default_block {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||
|
||||
let mut stream = run_block(
|
||||
&default_block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
scope,
|
||||
)
|
||||
.await?;
|
||||
|
||||
*results = Some({
|
||||
let values = stream.drain_vec().await;
|
||||
|
||||
let errors = context.get_errors();
|
||||
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value."))?;
|
||||
|
||||
Value {
|
||||
value: value.value.clone(),
|
||||
tag: input.tag.clone(),
|
||||
}
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_value(&input.tag)
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_value(&input.tag)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
ref tag,
|
||||
} => {
|
||||
if column_paths.is_empty() {
|
||||
Ok(OutputStream::one(ReturnSuccess::value({
|
||||
let is_empty = input.is_empty();
|
||||
|
||||
if default_block.is_some() {
|
||||
if is_empty {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||
} else {
|
||||
input.clone()
|
||||
}
|
||||
} else {
|
||||
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
let mut obj = input.clone();
|
||||
|
||||
for column in column_paths.clone() {
|
||||
let path = UntaggedValue::Primitive(Primitive::ColumnPath(column.clone()))
|
||||
.into_value(tag);
|
||||
let data = r.get_data(&as_string(&path)?).borrow().clone();
|
||||
let is_empty = data.is_empty();
|
||||
|
||||
let default = if default_block.is_some() {
|
||||
if is_empty {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||
} else {
|
||||
data.clone()
|
||||
}
|
||||
} else {
|
||||
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||
};
|
||||
|
||||
if let Ok(value) =
|
||||
obj.swap_data_by_column_path(&column, Box::new(move |_| Ok(default)))
|
||||
{
|
||||
obj = value;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(obj)))
|
||||
}
|
||||
}
|
||||
other => Ok(OutputStream::one(ReturnSuccess::value({
|
||||
if other.is_empty() {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(other.tag))
|
||||
} else {
|
||||
UntaggedValue::boolean(false).into_value(other.tag)
|
||||
}
|
||||
}))),
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -191,11 +191,12 @@ async fn enter(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Enter;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Enter {})
|
||||
Ok(test_examples(Enter {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -93,11 +93,12 @@ async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Every;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Every {})
|
||||
Ok(test_examples(Every {})?)
|
||||
}
|
||||
}
|
||||
|
87
crates/nu-cli/src/commands/exec.rs
Normal file
87
crates/nu-cli/src/commands/exec.rs
Normal 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(®istry).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,
|
||||
))
|
||||
}
|
@ -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};
|
||||
@ -63,11 +63,12 @@ pub async fn exit(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Exit;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Exit {})
|
||||
Ok(test_examples(Exit {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -72,11 +72,12 @@ async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::First;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(First {})
|
||||
Ok(test_examples(First {})?)
|
||||
}
|
||||
}
|
||||
|
192
crates/nu-cli/src/commands/flatten.rs
Normal file
192
crates/nu-cli/src/commands/flatten.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"flatten"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Flatten the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
flatten(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "flatten a table",
|
||||
example: "echo [[N, u, s, h, e, l, l]] | flatten | first",
|
||||
result: Some(vec![Value::from("N")]),
|
||||
},
|
||||
Example {
|
||||
description: "flatten a column having a nested table",
|
||||
example: "echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal",
|
||||
result: Some(vec![Value::from("arepa")]),
|
||||
},
|
||||
Example {
|
||||
description: "restrict the flattening by passing column names",
|
||||
example: "echo [[origin, crate, versions]; [World, $(echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions",
|
||||
result: Some(vec![Value::from("0.22")]),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn flatten(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (Arguments { rest: columns }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
futures::stream::iter(flat_value(&columns, &item, &tag).into_iter().flatten())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
enum TableInside<'a> {
|
||||
Entries(&'a str, &'a Tag, Vec<&'a Value>),
|
||||
}
|
||||
|
||||
fn flat_value(
|
||||
columns: &[Tagged<String>],
|
||||
item: &Value,
|
||||
name_tag: impl Into<Tag>,
|
||||
) -> Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError> {
|
||||
let tag = item.tag.clone();
|
||||
let name_tag = name_tag.into();
|
||||
|
||||
let res = {
|
||||
if item.is_row() {
|
||||
let mut out = TaggedDictBuilder::new(tag);
|
||||
let mut a_table = None;
|
||||
let mut tables_explicitly_flattened = 0;
|
||||
|
||||
for (column, value) in item.row_entries() {
|
||||
let column_requested = columns.iter().find(|c| c.item == *column);
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Row(Dictionary { entries: mapa }),
|
||||
..
|
||||
} = value
|
||||
{
|
||||
if column_requested.is_none() && !columns.is_empty() {
|
||||
if out.contains_key(&column) {
|
||||
out.insert_value(format!("{}_{}", column, column), value.clone());
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (k, v) in mapa.into_iter() {
|
||||
if out.contains_key(k) {
|
||||
out.insert_value(format!("{}_{}", column, k), v.clone());
|
||||
} else {
|
||||
out.insert_value(k, v.clone());
|
||||
}
|
||||
}
|
||||
} else if value.is_table() {
|
||||
if tables_explicitly_flattened >= 1 && column_requested.is_some() {
|
||||
let attempted = if let Some(name) = column_requested {
|
||||
name.span()
|
||||
} else {
|
||||
name_tag.span
|
||||
};
|
||||
|
||||
let already_flattened =
|
||||
if let Some(TableInside::Entries(_, column_tag, _)) = a_table {
|
||||
column_tag.span
|
||||
} else {
|
||||
name_tag.span
|
||||
};
|
||||
|
||||
return Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Error(ShellError::labeled_error_with_secondary(
|
||||
"can only flatten one inner table at the same time",
|
||||
"tried flattening more than one column with inner tables",
|
||||
attempted,
|
||||
"...but is flattened already",
|
||||
already_flattened,
|
||||
))
|
||||
.into_value(name_tag),
|
||||
)]);
|
||||
}
|
||||
|
||||
if !columns.is_empty() {
|
||||
if let Some(requested) = column_requested {
|
||||
a_table = Some(TableInside::Entries(
|
||||
&requested.item,
|
||||
&requested.tag,
|
||||
value.table_entries().collect(),
|
||||
));
|
||||
|
||||
tables_explicitly_flattened += 1;
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
} else if a_table.is_none() {
|
||||
a_table = Some(TableInside::Entries(
|
||||
&column,
|
||||
&value.tag,
|
||||
value.table_entries().collect(),
|
||||
))
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut expanded = vec![];
|
||||
|
||||
if let Some(TableInside::Entries(column, _, entries)) = a_table {
|
||||
for entry in entries.into_iter() {
|
||||
let mut base = out.clone();
|
||||
base.insert_value(column, entry.clone());
|
||||
expanded.push(base.into_value());
|
||||
}
|
||||
} else {
|
||||
expanded.push(out.into_value());
|
||||
}
|
||||
|
||||
expanded
|
||||
} else if item.is_table() {
|
||||
item.table_entries().map(Clone::clone).collect()
|
||||
} else {
|
||||
vec![item.clone()]
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res.into_iter().map(ReturnSuccess::value).collect())
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
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;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_protocol::{ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
@ -54,7 +54,7 @@ async fn format_command(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let scope = args.call_info.scope.clone();
|
||||
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
@ -83,9 +83,7 @@ async fn format_command(
|
||||
let result = evaluate_baseline_expr(
|
||||
&full_column_path.0,
|
||||
®istry,
|
||||
&value,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
Scope::append_var(scope.clone(), "$it", value.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -153,11 +151,12 @@ fn format(input: &str) -> Vec<FormatCommand> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Format;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Format {})
|
||||
Ok(test_examples(Format {})?)
|
||||
}
|
||||
}
|
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
|
||||
UntaggedValue::Primitive, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
|
||||
pub struct FileSize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
field: ColumnPath,
|
||||
format: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FileSize {
|
||||
fn name(&self) -> &str {
|
||||
"format filesize"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format filesize")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"format value",
|
||||
SyntaxShape::String,
|
||||
"the format into which convert the filesizes",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts a column of filesizes to some specified format"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
filesize(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the size row to KB",
|
||||
example: "ls | format filesize size KB",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert the apparent row to B",
|
||||
example: "du | format filesize apparent B",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
input: Value,
|
||||
format: Tagged<String>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok({
|
||||
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||
match replace_for {
|
||||
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
|
||||
Ok(b) => OutputStream::one(ReturnSuccess::value(
|
||||
input.replace_data_at_column_path(&field, b).expect("Given that the existance check was already done, this souldn't trigger never"),
|
||||
)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn filesize(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (Arguments { field, format }, input) = raw_args.process(®istry).await?;
|
||||
let field = Arc::new(field);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let format = format.clone();
|
||||
let field = field.clone();
|
||||
|
||||
async {
|
||||
match process_row(input, format, field).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn convert_bytes_to_string_using_format(
|
||||
bytes: Value,
|
||||
format: Tagged<String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match bytes.value {
|
||||
Primitive(Filesize(b)) => {
|
||||
let byte = byte_unit::Byte::from_bytes(b as u128);
|
||||
let value = match format.item().to_lowercase().as_str() {
|
||||
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
|
||||
"kb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
|
||||
)),
|
||||
"kib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
|
||||
)),
|
||||
"mb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
|
||||
)),
|
||||
"mib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
|
||||
)),
|
||||
"gb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
|
||||
)),
|
||||
"gib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
|
||||
)),
|
||||
"tb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
|
||||
)),
|
||||
"tib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
|
||||
)),
|
||||
"pb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
|
||||
)),
|
||||
"pib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
|
||||
)),
|
||||
"eb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
|
||||
)),
|
||||
"eib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
|
||||
)),
|
||||
"zb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
|
||||
)),
|
||||
"zib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
|
||||
)),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
format!("Invalid format code: {:}", format.item()),
|
||||
"invalid format",
|
||||
format.tag(),
|
||||
)),
|
||||
};
|
||||
match value {
|
||||
Ok(b) => Ok(Value { value: b, ..bytes }),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"the data in this row is not of the type filesize",
|
||||
"invalid row type",
|
||||
bytes.tag(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FileSize;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(FileSize {})?)
|
||||
}
|
||||
}
|
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod command;
|
||||
pub mod format_filesize;
|
||||
|
||||
pub use command::Format;
|
||||
pub use format_filesize::FileSize;
|
@ -35,11 +35,12 @@ impl WholeStreamCommand for From {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::From;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(From {})
|
||||
Ok(test_examples(From {})?)
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +109,12 @@ async fn from_csv(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromCSV;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromCSV {})
|
||||
Ok(test_examples(FromCSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -130,11 +130,12 @@ async fn from_eml(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromEML;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromEML {})
|
||||
Ok(test_examples(FromEML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -249,11 +249,12 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromIcs;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromIcs {})
|
||||
Ok(test_examples(FromIcs {})?)
|
||||
}
|
||||
}
|
||||
|
@ -95,11 +95,12 @@ async fn from_ini(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromINI;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromINI {})
|
||||
Ok(test_examples(FromINI {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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) => {
|
||||
@ -143,11 +144,12 @@ async fn from_json(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromJSON;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromJSON {})
|
||||
Ok(test_examples(FromJSON {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
@ -101,11 +102,12 @@ async fn from_ods(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromODS;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromODS {})
|
||||
Ok(test_examples(FromODS {})?)
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ fn parse_aligned_columns<'a>(
|
||||
.flat_map(|s| find_indices(*s))
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
indices.sort();
|
||||
indices.sort_unstable();
|
||||
indices.dedup();
|
||||
|
||||
let headers: Vec<(String, usize)> = indices
|
||||
@ -302,7 +302,9 @@ async fn from_ssv(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::*;
|
||||
|
||||
fn owned(x: &str, y: &str) -> (String, String) {
|
||||
(String::from(x), String::from(y))
|
||||
}
|
||||
@ -504,10 +506,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use super::FromSSV;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromSSV {})
|
||||
Ok(test_examples(FromSSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
@ -100,11 +101,12 @@ pub async fn from_toml(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTOML;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTOML {})
|
||||
Ok(test_examples(FromTOML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +52,12 @@ async fn from_tsv(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTSV;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTSV {})
|
||||
Ok(test_examples(FromTSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -64,11 +64,12 @@ async fn from_url(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromURL;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromURL {})
|
||||
Ok(test_examples(FromURL {})?)
|
||||
}
|
||||
}
|
||||
|
@ -104,11 +104,12 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromVcf;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromVcf {})
|
||||
Ok(test_examples(FromVcf {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
@ -101,11 +102,12 @@ async fn from_xlsx(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromXLSX;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXLSX {})
|
||||
Ok(test_examples(FromXLSX {})?)
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,7 @@ async fn from_xml(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use crate::commands::from_xml;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
@ -304,10 +305,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use super::FromXML;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXML {})
|
||||
Ok(test_examples(FromXML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -171,15 +173,16 @@ async fn from_yaml(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::*;
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::row;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromYAML {})
|
||||
Ok(test_examples(FromYAML {})?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -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,17 +58,95 @@ impl WholeStreamCommand for Get {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
let fields = path.clone();
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: column_paths }, mut input) = args.process(®istry).await?;
|
||||
if column_paths.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
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);
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
trace!("get {:?}", column_paths);
|
||||
let output_stream = input
|
||||
.map(move |item| {
|
||||
let output = column_paths
|
||||
.iter()
|
||||
.map(move |path| get_output(&item, path))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream();
|
||||
Ok(output_stream)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
|
||||
match get_column_path(path, item) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
}) => vec![],
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
|
||||
Ok(other) => vec![ReturnSuccess::value(other)],
|
||||
Err(reason) => vec![ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
|
||||
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
UntaggedValue::Table(rows) => {
|
||||
return get_column_path_from_table_error(
|
||||
rows,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
);
|
||||
}
|
||||
UntaggedValue::Row(columns) => {
|
||||
if let Some(error) = get_column_from_row_error(
|
||||
columns,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
obj_source,
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.as_string()) {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0]),
|
||||
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),
|
||||
..
|
||||
@ -77,8 +155,8 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.filter_map(|r| did_you_mean(&r, column_path_tried.as_string()))
|
||||
.map(|s| s[0].to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
@ -93,15 +171,15 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
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(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
@ -112,11 +190,11 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(",")
|
||||
names.join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
@ -130,134 +208,71 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.as_string()) {
|
||||
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(",")
|
||||
suggestions[0],
|
||||
&obj_source.data_descriptors().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
} => 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(",")
|
||||
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(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
Ok(test_examples(Get {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::suggestions::suggestions;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::as_string;
|
||||
|
||||
pub struct GroupBy;
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByArgs {
|
||||
pub struct Arguments {
|
||||
grouper: Option<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupBy {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
@ -28,7 +29,7 @@ impl WholeStreamCommand for GroupBy {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"create a new table grouped."
|
||||
"Create a new table grouped."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -39,17 +40,45 @@ impl WholeStreamCommand for GroupBy {
|
||||
group_by(args, registry).await
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_data::value::date_naive_from_str as date;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "group items by column named \"type\"",
|
||||
example: r#"ls | group-by type"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "blocks can be used for generating a grouping key (same as above)",
|
||||
example: r#"ls | group-by { get type }"#,
|
||||
result: None,
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"File".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||
"type".to_string() => UntaggedValue::string("File").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(10).into(),
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||
"type".to_string() => UntaggedValue::string("File").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(20).into(),
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
]).into(),
|
||||
"Dir".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".to_string() => UntaggedValue::string("Jonathan").into(),
|
||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(5).into(),
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".to_string() => UntaggedValue::string("Yehuda").into(),
|
||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(4).into(),
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
]).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "you can also group by raw values by leaving out the argument",
|
||||
@ -74,10 +103,26 @@ impl WholeStreamCommand for GroupBy {
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "write pipelines for a more involved grouping key",
|
||||
example:
|
||||
"echo [1 3 1 3 2 1 1] | group-by { echo `({{$it}} - 1) % 3` | calc | str from }",
|
||||
result: None,
|
||||
description:
|
||||
"use the block form to generate a grouping key when each row gets processed",
|
||||
example: "echo [1 3 1 3 2 1 1] | group-by { = ($it - 1) mod 3 }",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"0".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
|
||||
]).into(),
|
||||
"2".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]).into(),
|
||||
"1".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
]).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -94,10 +139,9 @@ pub async fn group_by(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
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, ®istry));
|
||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||
let scope = args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let (Arguments { grouper }, input) = args.process(®istry).await?;
|
||||
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let mut keys: Vec<Result<String, ShellError>> = vec![];
|
||||
@ -114,12 +158,9 @@ pub async fn group_by(
|
||||
for value in values.iter() {
|
||||
let run = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
match crate::commands::each::process_row(run, scope, head, context, value.clone())
|
||||
.await
|
||||
{
|
||||
match crate::commands::each::process_row(run, scope, context, value.clone()).await {
|
||||
Ok(mut s) => {
|
||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
s.drain_vec().await;
|
||||
@ -167,6 +208,14 @@ pub async fn group_by(
|
||||
));
|
||||
}
|
||||
|
||||
let first = values[0].clone();
|
||||
|
||||
let name = if first.tag.anchor().is_some() {
|
||||
first.tag
|
||||
} else {
|
||||
name
|
||||
};
|
||||
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let group_value = match group_strategy {
|
||||
@ -179,39 +228,14 @@ pub async fn group_by(
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), name)
|
||||
}
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, &name),
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(group_value?)))
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(
|
||||
column_name: &Option<Tagged<String>>,
|
||||
values: &Value,
|
||||
@ -250,9 +274,10 @@ pub fn group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::group;
|
||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::committers;
|
||||
use nu_errors::ShellError;
|
||||
use nu_source::*;
|
||||
use nu_test_support::value::{date, int, row, string, table};
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||
@ -311,12 +336,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::GroupBy;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupBy {})
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::suggestions::suggestions;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
@ -137,39 +138,15 @@ pub async fn group_by_date(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GroupByDate;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupByDate {})
|
||||
Ok(test_examples(GroupByDate {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -116,11 +116,12 @@ pub async fn headers(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Headers;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Headers {})
|
||||
Ok(test_examples(Headers {})?)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nu_data::command::signature_dict;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Help;
|
||||
|
||||
@ -63,54 +63,115 @@ 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 (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(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
registry.get_command(&cmd_name).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
format!("Could not load {}", cmd_name),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
})?,
|
||||
name,
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
dict.insert_untagged("name", cmd_name);
|
||||
dict.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
value
|
||||
.get_data_by_key("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)),
|
||||
},
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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(
|
||||
®istry,
|
||||
@ -174,11 +235,12 @@ pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> Str
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Help;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Help {})
|
||||
Ok(test_examples(Help {})?)
|
||||
}
|
||||
}
|
||||
|
@ -112,8 +112,9 @@ pub async fn histogram(
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: None,
|
||||
format: &None,
|
||||
eval: &evaluate_with,
|
||||
reduction: &nu_data::utils::Reduction::Count,
|
||||
},
|
||||
&name,
|
||||
)?;
|
||||
@ -123,17 +124,33 @@ pub async fn histogram(
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
results
|
||||
.percentages
|
||||
.data
|
||||
.table_entries()
|
||||
.map(move |value| {
|
||||
let values = value.table_entries().cloned().collect::<Vec<_>>();
|
||||
let occurrences = values.len();
|
||||
|
||||
(occurrences, values[occurrences - 1].clone())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(move |(occurrences, value)| {
|
||||
.zip(
|
||||
results
|
||||
.percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
)
|
||||
.map(move |(counts, percentages)| {
|
||||
let percentage = percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| {
|
||||
UntaggedValue::decimal_from_float(0.0, name.span).into_value(&name)
|
||||
});
|
||||
let value = counts
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| UntaggedValue::int(0).into_value(&name));
|
||||
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let column_value = labels
|
||||
.get(idx)
|
||||
@ -147,19 +164,19 @@ pub async fn histogram(
|
||||
.clone();
|
||||
|
||||
fact.insert_value(&column.item, column_value);
|
||||
fact.insert_untagged("occurrences", UntaggedValue::int(occurrences));
|
||||
fact.insert_untagged("count", value);
|
||||
|
||||
let percentage = format!(
|
||||
let fmt_percentage = format!(
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::str_::from::action(&value, &name, Some(2), true)?
|
||||
crate::commands::str_::from::action(&percentage, &name, Some(2), true)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
|
||||
|
||||
let string = std::iter::repeat("*")
|
||||
.take(value.as_u64().map_err(|_| {
|
||||
.take(percentage.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
@ -178,11 +195,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),
|
||||
@ -214,11 +227,12 @@ fn splitter(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Histogram;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Histogram {})
|
||||
Ok(test_examples(Histogram {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -80,11 +79,12 @@ fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStrea
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
Ok(test_examples(History {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
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;
|
||||
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||
};
|
||||
|
||||
pub struct If;
|
||||
|
||||
@ -72,9 +74,9 @@ async fn if_command(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
|
||||
let (
|
||||
IfArgs {
|
||||
@ -119,14 +121,12 @@ async fn if_command(
|
||||
let then_case = then_case.clone();
|
||||
let else_case = else_case.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let scope = Scope::append_var(scope.clone(), "$it", input);
|
||||
let mut context = context.clone();
|
||||
|
||||
async move {
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition =
|
||||
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
|
||||
.await;
|
||||
let condition = evaluate_baseline_expr(&condition, &*registry, scope.clone()).await;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
@ -136,9 +136,7 @@ async fn if_command(
|
||||
&then_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -151,9 +149,7 @@ async fn if_command(
|
||||
&else_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -178,11 +174,12 @@ async fn if_command(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::If;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(If {})
|
||||
Ok(test_examples(If {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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::{
|
||||
@ -9,16 +9,18 @@ use nu_protocol::{
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Insert;
|
||||
use indexmap::indexmap;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct InsertArgs {
|
||||
pub struct Arguments {
|
||||
column: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Insert {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
@ -44,11 +46,33 @@ impl WholeStreamCommand for Insert {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
insert(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Insert a column with a value",
|
||||
example: "echo [[author, commits]; ['Andrés', 1]] | insert branches 5",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"author".to_string() => Value::from("Andrés"),
|
||||
"commits".to_string() => UntaggedValue::int(1).into(),
|
||||
"branches".to_string() => UntaggedValue::int(5).into(),
|
||||
})
|
||||
.into()]),
|
||||
},Example {
|
||||
description: "Use in block form for more involved insertion logic",
|
||||
example: "echo [[author, lucky_number]; ['Yehuda', 4]] | insert success { = $it.lucky_number * 10 }",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"author".to_string() => Value::from("Yehuda"),
|
||||
"lucky_number".to_string() => UntaggedValue::int(4).into(),
|
||||
"success".to_string() => UntaggedValue::int(40).into(),
|
||||
})
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<Context>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
mut value: Arc<Value>,
|
||||
field: Arc<ColumnPath>,
|
||||
@ -63,15 +87,9 @@ async fn process_row(
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||
|
||||
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
@ -85,13 +103,16 @@ async fn process_row(
|
||||
let result = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to insert with"))?;
|
||||
.ok_or_else(|| ShellError::unexpected("No value to insert with."))?;
|
||||
|
||||
value.clone()
|
||||
Value {
|
||||
value: value.value.clone(),
|
||||
tag: input.tag.clone(),
|
||||
}
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
UntaggedValue::nothing().into_value(&input.tag)
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
UntaggedValue::table(&values).into_value(&input.tag)
|
||||
};
|
||||
|
||||
match input {
|
||||
@ -118,7 +139,11 @@ async fn process_row(
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match scope.it.insert_data_at_column_path(&field, value.clone()) {
|
||||
} => match scope
|
||||
.var("$it")
|
||||
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
|
||||
.insert_data_at_column_path(&field, value.clone())
|
||||
{
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
@ -135,9 +160,9 @@ async fn insert(
|
||||
registry: &CommandRegistry,
|
||||
) -> 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, ®istry));
|
||||
let (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (Arguments { column, value }, input) = raw_args.process(®istry).await?;
|
||||
let value = Arc::new(value);
|
||||
let column = Arc::new(column);
|
||||
|
||||
@ -158,15 +183,3 @@ async fn insert(
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Insert;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Insert {})
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl WholeStreamCommand for IntoInt {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert filesize to integer",
|
||||
example: "echo 1kb | into-int $it | = $it / 1024",
|
||||
example: "into-int 1kb | each { = $it / 1024 }",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
}]
|
||||
}
|
||||
@ -78,11 +78,12 @@ async fn into_int(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IntoInt;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IntoInt {})
|
||||
Ok(test_examples(IntoInt {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,211 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
enum IsEmptyFor {
|
||||
Value,
|
||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
||||
RowWithField(Tagged<ColumnPath>),
|
||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
||||
}
|
||||
|
||||
pub struct IsEmpty;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IsEmptyArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IsEmpty {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness followed by the replacement value.",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if rest.len() <= 2 {
|
||||
let field = rest.get(0);
|
||||
let replacement_if_true = rest.get(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Some(field), Some(replacement_if_true)) => {
|
||||
IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
)
|
||||
}
|
||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
)),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val = crate::commands::get::get_column_path(&field, &out)?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
obj.replace_data_at_column_path(&field, default.clone())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
};
|
||||
|
||||
out = emptiness_value?;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(out))
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IsEmpty;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IsEmpty {})
|
||||
}
|
||||
}
|
@ -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};
|
||||
@ -74,11 +74,12 @@ async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -33,7 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let scope = args.call_info.scope.clone();
|
||||
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
@ -85,19 +85,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let result = evaluate_baseline_expr(&*condition, ®istry, scope).await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
!matches!(result, Ok(ref v) if v.is_true())
|
||||
@ -109,12 +101,13 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -33,7 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let scope = args.call_info.scope.clone();
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
@ -84,20 +84,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let result = evaluate_baseline_expr(&*condition, ®istry, scope).await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
matches!(result, Ok(ref v) if v.is_true())
|
||||
@ -109,12 +100,13 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -121,11 +121,12 @@ async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Kill;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Kill {})
|
||||
Ok(test_examples(Kill {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
@ -83,11 +83,12 @@ async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Last;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Last {})
|
||||
Ok(test_examples(Last {})?)
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
} => {
|
||||
let mut leftover_string = leftover_string.lock();
|
||||
|
||||
let st = (&*leftover_string).clone() + &st;
|
||||
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
let lo_lines = (&*leftover_string).lines().map(|x| x.to_string());
|
||||
let st_lines = st.lines().map(|x| x.to_string());
|
||||
let mut lines: Vec<String> = lo_lines.chain(st_lines).collect();
|
||||
|
||||
leftover_string.clear();
|
||||
|
||||
@ -125,11 +125,12 @@ async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Lines;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Lines {})
|
||||
Ok(test_examples(Lines {})?)
|
||||
}
|
||||
}
|
||||
|
@ -89,11 +89,12 @@ impl WholeStreamCommand for Ls {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ls;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ls {})
|
||||
Ok(test_examples(Ls {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
..
|
||||
@ -162,12 +188,13 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user