mirror of
https://github.com/nushell/nushell.git
synced 2025-07-03 16:13:20 +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
|
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||||
condition: eq(variables['style'], 'canary')
|
condition: eq(variables['style'], 'canary')
|
||||||
displayName: Check clippy lints
|
displayName: Check clippy lints
|
||||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
|
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||||
condition: eq(variables['style'], 'minimal')
|
condition: eq(variables['style'], 'minimal')
|
||||||
displayName: Run tests
|
displayName: Run tests
|
||||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||||
|
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
|
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:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -60,3 +65,11 @@ cargo build
|
|||||||
```shell
|
```shell
|
||||||
cargo fmt --all
|
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"
|
name = "nu"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
version = "0.19.0"
|
version = "0.22.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*/"]
|
members = ["crates/*/"]
|
||||||
@ -18,51 +18,58 @@ members = ["crates/*/"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"}
|
nu-cli = {version = "0.22.0", path = "./crates/nu-cli"}
|
||||||
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
|
nu-data = {version = "0.22.0", path = "./crates/nu-data"}
|
||||||
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
|
nu-errors = {version = "0.22.0", path = "./crates/nu-errors"}
|
||||||
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
|
nu-parser = {version = "0.22.0", path = "./crates/nu-parser"}
|
||||||
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
|
nu-plugin = {version = "0.22.0", path = "./crates/nu-plugin"}
|
||||||
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
|
nu-protocol = {version = "0.22.0", path = "./crates/nu-protocol"}
|
||||||
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
|
nu-source = {version = "0.22.0", path = "./crates/nu-source"}
|
||||||
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
|
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_binaryview = {version = "0.22.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||||
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true}
|
nu_plugin_chart = {version = "0.22.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||||
nu_plugin_from_bson = {version = "0.19.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
nu_plugin_fetch = {version = "0.22.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||||
nu_plugin_from_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
nu_plugin_from_bson = {version = "0.22.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||||
nu_plugin_inc = {version = "0.19.0", path = "./crates/nu_plugin_inc", optional = true}
|
nu_plugin_from_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||||
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
|
nu_plugin_inc = {version = "0.22.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||||
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
|
nu_plugin_match = {version = "0.22.0", path = "./crates/nu_plugin_match", optional = true}
|
||||||
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
|
nu_plugin_post = {version = "0.22.0", path = "./crates/nu_plugin_post", optional = true}
|
||||||
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
|
nu_plugin_ps = {version = "0.22.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||||
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
|
nu_plugin_s3 = {version = "0.22.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||||
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
|
nu_plugin_start = {version = "0.22.0", path = "./crates/nu_plugin_start", optional = true}
|
||||||
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true}
|
nu_plugin_sys = {version = "0.22.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||||
nu_plugin_to_bson = {version = "0.19.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
nu_plugin_textview = {version = "0.22.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||||
nu_plugin_to_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
nu_plugin_to_bson = {version = "0.22.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
nu_plugin_tree = {version = "0.19.0", path = "./crates/nu_plugin_tree", 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}
|
# Required to bootstrap the main binary
|
||||||
semver = {version = "0.10.0", optional = true}
|
clap = "2.33.3"
|
||||||
url = {version = "2.1.1", optional = true}
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
clap = "2.33.1"
|
log = "0.4.11"
|
||||||
ctrlc = "3.1.4"
|
|
||||||
dunce = "1.0.1"
|
|
||||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
|
||||||
log = "0.4.8"
|
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
quick-xml = "0.18.1"
|
itertools = "0.9.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[build-dependencies]
|
||||||
serde = {version = "1.0.110", features = ["derive"]}
|
|
||||||
toml = "0.5.6"
|
|
||||||
|
|
||||||
[features]
|
[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 = [
|
default = [
|
||||||
"sys",
|
"sys",
|
||||||
"ps",
|
"ps",
|
||||||
@ -75,40 +82,39 @@ default = [
|
|||||||
"ptree-support",
|
"ptree-support",
|
||||||
"term-support",
|
"term-support",
|
||||||
"uuid-support",
|
"uuid-support",
|
||||||
|
"rustyline-support",
|
||||||
"match",
|
"match",
|
||||||
"post",
|
"post",
|
||||||
"fetch",
|
"fetch",
|
||||||
|
"rich-benchmark",
|
||||||
]
|
]
|
||||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
|
|
||||||
# Default
|
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||||
inc = ["semver", "nu_plugin_inc"]
|
|
||||||
ps = ["nu_plugin_ps"]
|
|
||||||
sys = ["nu_plugin_sys"]
|
|
||||||
textview = ["crossterm", "url", "nu_plugin_textview"]
|
|
||||||
|
|
||||||
# Stable
|
trace = ["nu-parser/trace"]
|
||||||
binaryview = ["nu_plugin_binaryview"]
|
|
||||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
# Stable (Default)
|
||||||
fetch = ["nu_plugin_fetch"]
|
fetch = ["nu_plugin_fetch"]
|
||||||
|
inc = ["nu_plugin_inc"]
|
||||||
match = ["nu_plugin_match"]
|
match = ["nu_plugin_match"]
|
||||||
post = ["nu_plugin_post"]
|
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"]
|
s3 = ["nu_plugin_s3"]
|
||||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||||
start = ["nu_plugin_start"]
|
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"]
|
trash-support = ["nu-cli/trash-support"]
|
||||||
uuid-support = ["nu-cli/uuid_crate"]
|
tree = ["nu_plugin_tree"]
|
||||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
xpath = ["nu_plugin_xpath"]
|
||||||
|
|
||||||
# Core plugins that ship with `cargo install nu` by default
|
# Core plugins that ship with `cargo install nu` by default
|
||||||
# Currently, Cargo limits us to installing only one binary
|
# 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"
|
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||||
required-features = ["s3"]
|
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
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
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.
|
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:
|
Required dependencies:
|
||||||
|
|
||||||
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
|
|||||||
name │ nu
|
name │ nu
|
||||||
readme │ README.md
|
readme │ README.md
|
||||||
repository │ https://github.com/nushell/nushell
|
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:
|
Finally, we can use commands outside of Nu once we have the data we want:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version | echo $it
|
> open Cargo.toml | get package.version
|
||||||
0.15.1
|
0.21.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
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"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.19.0"
|
version = "0.22.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-data = {version = "0.19.0", path = "../nu-data"}
|
nu-data = {version = "0.22.0", path = "../nu-data"}
|
||||||
nu-errors = {version = "0.19.0", path = "../nu-errors"}
|
nu-errors = {version = "0.22.0", path = "../nu-errors"}
|
||||||
nu-parser = {version = "0.19.0", path = "../nu-parser"}
|
nu-parser = {version = "0.22.0", path = "../nu-parser"}
|
||||||
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
|
nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
|
||||||
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
|
nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
|
||||||
nu-source = {version = "0.19.0", path = "../nu-source"}
|
nu-source = {version = "0.22.0", path = "../nu-source"}
|
||||||
nu-table = {version = "0.19.0", path = "../nu-table"}
|
nu-table = {version = "0.22.0", path = "../nu-table"}
|
||||||
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
|
nu-test-support = {version = "0.22.0", path = "../nu-test-support"}
|
||||||
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
|
nu-value-ext = {version = "0.22.0", path = "../nu-value-ext"}
|
||||||
|
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
app_dirs = {version = "2", package = "app_dirs2"}
|
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
async-trait = "0.1.36"
|
async-trait = "0.1.40"
|
||||||
base64 = "0.12.3"
|
base64 = "0.12.3"
|
||||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
byte-unit = "3.1.3"
|
byte-unit = "4.0.9"
|
||||||
bytes = "0.5.5"
|
bytes = "0.5.6"
|
||||||
calamine = "0.16"
|
calamine = "0.16.1"
|
||||||
chrono = {version = "0.4.11", features = ["serde"]}
|
chrono = {version = "0.4.15", features = ["serde"]}
|
||||||
clap = "2.33.1"
|
clap = "2.33.3"
|
||||||
codespan-reporting = "0.9.5"
|
codespan-reporting = "0.9.5"
|
||||||
csv = "1.1"
|
csv = "1.1.3"
|
||||||
ctrlc = {version = "3.1.4", optional = true}
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
derive-new = "0.5.8"
|
derive-new = "0.5.8"
|
||||||
directories = {version = "2.0.2", optional = true}
|
directories = {version = "3.0.1", optional = true}
|
||||||
dirs = {version = "2.0.2", optional = true}
|
dirs = {version = "3.0.1", optional = true}
|
||||||
dtparse = "1.1.0"
|
dtparse = "1.2.0"
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
fs_extra = "1.2.0"
|
||||||
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
futures-util = "0.3.5"
|
futures-util = "0.3.5"
|
||||||
futures_codec = "0.4"
|
futures_codec = "0.4.1"
|
||||||
getset = "0.1.1"
|
getset = "0.1.1"
|
||||||
git2 = {version = "0.13.6", default_features = false, optional = true}
|
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
hex = "0.4"
|
heim = {version = "0.1.0-beta.3", optional = true}
|
||||||
htmlescape = "0.3.1"
|
htmlescape = "0.3.1"
|
||||||
ical = "0.6.*"
|
ical = "0.6.0"
|
||||||
ichwh = {version = "0.3.4", optional = true}
|
ichwh = {version = "0.3.4", optional = true}
|
||||||
indexmap = {version = "1.4.0", features = ["serde-1"]}
|
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
log = "0.4.8"
|
log = "0.4.11"
|
||||||
meval = "0.2"
|
meval = "0.2.0"
|
||||||
natural = "0.5.0"
|
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
num-traits = "0.2.12"
|
||||||
num-traits = "0.2.11"
|
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
pin-utils = "0.1.0"
|
pin-utils = "0.1.0"
|
||||||
pretty-hex = "0.1.1"
|
pretty-hex = "0.2.0"
|
||||||
pretty_env_logger = "0.4.0"
|
ptree = {version = "0.3.0", optional = true}
|
||||||
ptree = {version = "0.2", optional = true}
|
|
||||||
query_interface = "0.3.5"
|
query_interface = "0.3.5"
|
||||||
rand = "0.7"
|
quick-xml = "0.18.1"
|
||||||
regex = "1"
|
rand = "0.7.3"
|
||||||
|
regex = "1.3.9"
|
||||||
roxmltree = "0.13.0"
|
roxmltree = "0.13.0"
|
||||||
rust-embed = "5.6.0"
|
rust-embed = "5.6.0"
|
||||||
rustyline = "6.2.0"
|
rustyline = {version = "6.3.0", optional = true}
|
||||||
serde = {version = "1.0.114", features = ["derive"]}
|
serde = {version = "1.0.115", features = ["derive"]}
|
||||||
serde-hjson = "0.9.1"
|
serde-hjson = "0.9.1"
|
||||||
serde_bytes = "0.11.5"
|
serde_bytes = "0.11.5"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.55"
|
serde_json = "1.0.57"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8.13"
|
||||||
sha2 = "0.9.1"
|
sha2 = "0.9.1"
|
||||||
shellexpand = "2.0.0"
|
shellexpand = "2.0.0"
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
|
sxd-document = "0.3.2"
|
||||||
|
sxd-xpath = "0.4.2"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
term = {version = "0.5.2", optional = true}
|
term = {version = "0.6.1", optional = true}
|
||||||
term_size = "0.3.2"
|
term_size = "0.3.2"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
typetag = "0.1.5"
|
unicode-segmentation = "1.6.0"
|
||||||
umask = "1.0.0"
|
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||||
unicode-xid = "0.2.1"
|
|
||||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||||
which = {version = "4.0.2", optional = true}
|
which = {version = "4.0.2", optional = true}
|
||||||
zip = {version = "0.5.6", optional = true}
|
zip = {version = "0.5.7", optional = true}
|
||||||
|
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"
|
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]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
umask = "1.0.0"
|
||||||
users = "0.10.0"
|
users = "0.10.0"
|
||||||
|
|
||||||
# TODO this will be possible with new dependency resolver
|
# TODO this will be possible with new dependency resolver
|
||||||
@ -112,17 +113,18 @@ users = "0.10.0"
|
|||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
features = ["bundled", "blob"]
|
features = ["bundled", "blob"]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.23.1"
|
version = "0.24.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
git2 = {version = "0.13", optional = true}
|
git2 = {version = "0.13.11", optional = true}
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9.2"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
clipboard-cli = ["clipboard"]
|
clipboard-cli = ["clipboard"]
|
||||||
|
rich-benchmark = ["heim"]
|
||||||
|
rustyline-support = ["rustyline"]
|
||||||
stable = []
|
stable = []
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
@ -5,7 +5,7 @@ fn main() -> Result<(), io::Error> {
|
|||||||
let out_dir = env::var_os("OUT_DIR").expect(
|
let out_dir = env::var_os("OUT_DIR").expect(
|
||||||
"\
|
"\
|
||||||
OUT_DIR environment variable not found. \
|
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\
|
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::block::run_block;
|
||||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||||
use crate::context::Context;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::git::current_branch;
|
|
||||||
use crate::path::canonicalize;
|
use crate::path::canonicalize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
#[cfg(feature = "rustyline-support")]
|
||||||
use crate::shell::Helper;
|
use crate::shell::Helper;
|
||||||
use crate::EnvironmentSyncer;
|
use crate::EnvironmentSyncer;
|
||||||
use futures_codec::FramedRead;
|
use futures_codec::FramedRead;
|
||||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
#[cfg(feature = "rustyline-support")]
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::{
|
||||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
self,
|
||||||
|
config::Configurer,
|
||||||
|
config::{ColorMode, CompletionType, Config},
|
||||||
|
error::ReadlineError,
|
||||||
|
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||||
|
};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
|
||||||
if let Ok(plugins) = crate::plugin::scan() {
|
|
||||||
context.add_commands(
|
|
||||||
plugins
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| !context.is_command_registered(p.name()))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@ -64,21 +56,16 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
|||||||
search_paths
|
search_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_default_context(
|
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||||
syncer: &mut crate::EnvironmentSyncer,
|
let mut context = EvaluationContext::basic()?;
|
||||||
interactive: bool,
|
|
||||||
) -> Result<Context, Box<dyn Error>> {
|
|
||||||
syncer.load_environment();
|
|
||||||
|
|
||||||
let mut context = Context::basic()?;
|
|
||||||
syncer.sync_env_vars(&mut context);
|
|
||||||
syncer.sync_path_vars(&mut context);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
use crate::commands::*;
|
use crate::commands::*;
|
||||||
|
|
||||||
context.add_commands(vec![
|
context.add_commands(vec![
|
||||||
|
whole_stream_command(NuPlugin),
|
||||||
// System/file operations
|
// System/file operations
|
||||||
|
whole_stream_command(Exec),
|
||||||
whole_stream_command(Pwd),
|
whole_stream_command(Pwd),
|
||||||
whole_stream_command(Ls),
|
whole_stream_command(Ls),
|
||||||
whole_stream_command(Du),
|
whole_stream_command(Du),
|
||||||
@ -108,7 +95,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Kill),
|
whole_stream_command(Kill),
|
||||||
whole_stream_command(Version),
|
whole_stream_command(Version),
|
||||||
whole_stream_command(Clear),
|
whole_stream_command(Clear),
|
||||||
whole_stream_command(What),
|
whole_stream_command(Describe),
|
||||||
whole_stream_command(Which),
|
whole_stream_command(Which),
|
||||||
whole_stream_command(Debug),
|
whole_stream_command(Debug),
|
||||||
whole_stream_command(Alias),
|
whole_stream_command(Alias),
|
||||||
@ -127,6 +114,8 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Shells),
|
whole_stream_command(Shells),
|
||||||
whole_stream_command(Enter),
|
whole_stream_command(Enter),
|
||||||
whole_stream_command(Exit),
|
whole_stream_command(Exit),
|
||||||
|
// Viz
|
||||||
|
whole_stream_command(Chart),
|
||||||
// Viewers
|
// Viewers
|
||||||
whole_stream_command(Autoview),
|
whole_stream_command(Autoview),
|
||||||
whole_stream_command(Table),
|
whole_stream_command(Table),
|
||||||
@ -136,7 +125,6 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(SplitRow),
|
whole_stream_command(SplitRow),
|
||||||
whole_stream_command(SplitChars),
|
whole_stream_command(SplitChars),
|
||||||
whole_stream_command(Lines),
|
whole_stream_command(Lines),
|
||||||
whole_stream_command(Trim),
|
|
||||||
whole_stream_command(Echo),
|
whole_stream_command(Echo),
|
||||||
whole_stream_command(Parse),
|
whole_stream_command(Parse),
|
||||||
whole_stream_command(Str),
|
whole_stream_command(Str),
|
||||||
@ -159,7 +147,9 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(StrEndsWith),
|
whole_stream_command(StrEndsWith),
|
||||||
whole_stream_command(StrCollect),
|
whole_stream_command(StrCollect),
|
||||||
whole_stream_command(StrLength),
|
whole_stream_command(StrLength),
|
||||||
|
whole_stream_command(StrLPad),
|
||||||
whole_stream_command(StrReverse),
|
whole_stream_command(StrReverse),
|
||||||
|
whole_stream_command(StrRPad),
|
||||||
whole_stream_command(StrCamelCase),
|
whole_stream_command(StrCamelCase),
|
||||||
whole_stream_command(StrPascalCase),
|
whole_stream_command(StrPascalCase),
|
||||||
whole_stream_command(StrKebabCase),
|
whole_stream_command(StrKebabCase),
|
||||||
@ -169,7 +159,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Ansi),
|
whole_stream_command(Ansi),
|
||||||
whole_stream_command(Char),
|
whole_stream_command(Char),
|
||||||
// Column manipulation
|
// Column manipulation
|
||||||
whole_stream_command(MoveColumn),
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Reject),
|
whole_stream_command(Reject),
|
||||||
whole_stream_command(Select),
|
whole_stream_command(Select),
|
||||||
whole_stream_command(Get),
|
whole_stream_command(Get),
|
||||||
@ -190,6 +180,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Nth),
|
whole_stream_command(Nth),
|
||||||
whole_stream_command(Drop),
|
whole_stream_command(Drop),
|
||||||
whole_stream_command(Format),
|
whole_stream_command(Format),
|
||||||
|
whole_stream_command(FileSize),
|
||||||
whole_stream_command(Where),
|
whole_stream_command(Where),
|
||||||
whole_stream_command(If),
|
whole_stream_command(If),
|
||||||
whole_stream_command(Compact),
|
whole_stream_command(Compact),
|
||||||
@ -204,8 +195,11 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Rename),
|
whole_stream_command(Rename),
|
||||||
whole_stream_command(Uniq),
|
whole_stream_command(Uniq),
|
||||||
whole_stream_command(Each),
|
whole_stream_command(Each),
|
||||||
whole_stream_command(IsEmpty),
|
whole_stream_command(EachGroup),
|
||||||
|
whole_stream_command(EachWindow),
|
||||||
|
whole_stream_command(Empty),
|
||||||
// Table manipulation
|
// Table manipulation
|
||||||
|
whole_stream_command(Flatten),
|
||||||
whole_stream_command(Move),
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Merge),
|
whole_stream_command(Merge),
|
||||||
whole_stream_command(Shuffle),
|
whole_stream_command(Shuffle),
|
||||||
@ -229,6 +223,9 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(MathSummation),
|
whole_stream_command(MathSummation),
|
||||||
whole_stream_command(MathVariance),
|
whole_stream_command(MathVariance),
|
||||||
whole_stream_command(MathProduct),
|
whole_stream_command(MathProduct),
|
||||||
|
whole_stream_command(MathRound),
|
||||||
|
whole_stream_command(MathFloor),
|
||||||
|
whole_stream_command(MathCeil),
|
||||||
// File format output
|
// File format output
|
||||||
whole_stream_command(To),
|
whole_stream_command(To),
|
||||||
whole_stream_command(ToCSV),
|
whole_stream_command(ToCSV),
|
||||||
@ -265,6 +262,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(RandomDice),
|
whole_stream_command(RandomDice),
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
whole_stream_command(RandomUUID),
|
whole_stream_command(RandomUUID),
|
||||||
|
whole_stream_command(RandomInteger),
|
||||||
// Path
|
// Path
|
||||||
whole_stream_command(PathBasename),
|
whole_stream_command(PathBasename),
|
||||||
whole_stream_command(PathCommand),
|
whole_stream_command(PathCommand),
|
||||||
@ -280,9 +278,10 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(UrlPath),
|
whole_stream_command(UrlPath),
|
||||||
whole_stream_command(UrlHost),
|
whole_stream_command(UrlHost),
|
||||||
whole_stream_command(UrlQuery),
|
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)]);
|
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>,
|
pipelines: Vec<String>,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut syncer = crate::EnvironmentSyncer::new();
|
let mut syncer = EnvironmentSyncer::new();
|
||||||
let mut context = create_default_context(&mut syncer, false)?;
|
let mut context = create_default_context(false)?;
|
||||||
|
let config = syncer.get_config();
|
||||||
|
|
||||||
let _ = register_plugins(&mut context);
|
context.configure(&config, |_, ctx| {
|
||||||
|
syncer.load_environment();
|
||||||
|
syncer.sync_env_vars(ctx);
|
||||||
|
syncer.sync_path_vars(ctx);
|
||||||
|
|
||||||
|
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")]
|
#[cfg(feature = "ctrlc")]
|
||||||
{
|
{
|
||||||
let cc = context.ctrl_c.clone();
|
let cc = _context.ctrl_c.clone();
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
cc.store(true, Ordering::SeqCst);
|
cc.store(true, Ordering::SeqCst);
|
||||||
})
|
})?;
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
|
|
||||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// before we start up, let's run our startup commands
|
Ok(())
|
||||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
}
|
||||||
if let Some(commands) = config.get("startup") {
|
|
||||||
|
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 {
|
match commands {
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Table(pipelines),
|
value: UntaggedValue::Table(pipelines),
|
||||||
@ -324,36 +592,29 @@ pub async fn run_vec_of_pipelines(
|
|||||||
} => {
|
} => {
|
||||||
for pipeline in pipelines {
|
for pipeline in pipelines {
|
||||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||||
let _ = run_pipeline_standalone(
|
let _ =
|
||||||
pipeline_string,
|
run_pipeline_standalone(pipeline_string, false, context, false).await;
|
||||||
false,
|
|
||||||
&mut 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_pipeline_standalone(
|
pub async fn run_pipeline_standalone(
|
||||||
pipeline: String,
|
pipeline: String,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
exit_on_error: bool,
|
exit_on_error: bool,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> 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 {
|
match line {
|
||||||
LineResult::Success(line) => {
|
LineResult::Success(line) => {
|
||||||
@ -391,7 +652,8 @@ pub async fn run_pipeline_standalone(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
#[cfg(feature = "rustyline-support")]
|
||||||
|
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||||
#[cfg(not(windows))]
|
#[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)),
|
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
|
// 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
|
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||||
rl.set_max_history_size(100);
|
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);
|
println!("Error loading keybindings: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = match config::config(Tag::unknown()) {
|
rl
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Config could not be loaded.");
|
|
||||||
if let ShellError {
|
|
||||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
|
||||||
..
|
|
||||||
} = e
|
|
||||||
{
|
|
||||||
eprintln!("{}", diagnostic.message);
|
|
||||||
}
|
}
|
||||||
IndexMap::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(config) = config::config(Tag::unknown()) {
|
#[cfg(feature = "rustyline-support")]
|
||||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
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() {
|
for (idx, value) in line_editor_vars.row_entries() {
|
||||||
match idx.as_ref() {
|
match idx.as_ref() {
|
||||||
"max_history_size" => {
|
"max_history_size" => {
|
||||||
@ -500,9 +762,7 @@ pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Val
|
|||||||
"edit_mode" => {
|
"edit_mode" => {
|
||||||
let edit_mode = match value.as_string() {
|
let edit_mode = match value.as_string() {
|
||||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||||
Ok(s) if s.to_lowercase() == "emacs" => {
|
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||||
rustyline::config::EditMode::Emacs
|
|
||||||
}
|
|
||||||
_ => rustyline::config::EditMode::Emacs,
|
_ => rustyline::config::EditMode::Emacs,
|
||||||
};
|
};
|
||||||
rl.set_edit_mode(edit_mode);
|
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" => {
|
Ok(s) if s.to_lowercase() == "audible" => {
|
||||||
rustyline::config::BellStyle::Audible
|
rustyline::config::BellStyle::Audible
|
||||||
}
|
}
|
||||||
Ok(s) if s.to_lowercase() == "none" => {
|
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||||
rustyline::config::BellStyle::None
|
|
||||||
}
|
|
||||||
Ok(s) if s.to_lowercase() == "visible" => {
|
Ok(s) if s.to_lowercase() == "visible" => {
|
||||||
rustyline::config::BellStyle::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() {
|
let color_mode = match value.as_string() {
|
||||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
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() == "forced" => rustyline::ColorMode::Forced,
|
||||||
Ok(s) if s.to_lowercase() == "disabled" => {
|
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||||
rustyline::ColorMode::Disabled
|
|
||||||
}
|
|
||||||
_ => rustyline::ColorMode::Enabled,
|
_ => rustyline::ColorMode::Enabled,
|
||||||
};
|
};
|
||||||
rl.set_color_mode(color_mode);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
#[cfg(feature = "rustyline-support")]
|
||||||
// Show hints unless explicitly disabled in config
|
fn nu_line_editor_helper(
|
||||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
context: &mut EvaluationContext,
|
||||||
|
config: &dyn nu_data::config::Conf,
|
||||||
|
) -> crate::shell::Helper {
|
||||||
|
let hinter = rustyline_hinter(config);
|
||||||
|
crate::shell::Helper::new(context.clone(), hinter)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustyline-support")]
|
||||||
|
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||||
|
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||||
for (idx, value) in line_editor_vars.row_entries() {
|
for (idx, value) in line_editor_vars.row_entries() {
|
||||||
if idx == "show_hints" && value.expect_string() == "false" {
|
if idx == "show_hints" && value.expect_string() == "false" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(rustyline::hint::HistoryHinter {})
|
Some(rustyline::hint::HistoryHinter {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,11 +845,12 @@ fn chomp_newline(s: &str) -> &str {
|
|||||||
pub enum LineResult {
|
pub enum LineResult {
|
||||||
Success(String),
|
Success(String),
|
||||||
Error(String, ShellError),
|
Error(String, ShellError),
|
||||||
CtrlC,
|
|
||||||
Break,
|
Break,
|
||||||
|
CtrlC,
|
||||||
|
CtrlD,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, ShellError> {
|
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||||
let line = if line.ends_with('\n') {
|
let line = if line.ends_with('\n') {
|
||||||
&line[..line.len() - 1]
|
&line[..line.len() - 1]
|
||||||
} else {
|
} else {
|
||||||
@ -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)?;
|
let lite_result = nu_parser::lite_parse(&line, 0)?;
|
||||||
|
|
||||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
// 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());
|
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||||
classified_block.block.expand_it_usage();
|
|
||||||
|
|
||||||
let input_stream = InputStream::empty();
|
let input_stream = InputStream::empty();
|
||||||
let env = ctx.get_env();
|
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,
|
&classified_block.block,
|
||||||
ctx,
|
ctx,
|
||||||
input_stream,
|
input_stream,
|
||||||
&Value::nothing(),
|
Scope::from_env(env),
|
||||||
&IndexMap::new(),
|
|
||||||
&env,
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.collect_string(Tag::unknown())
|
.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
|
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||||
pub async fn process_line(
|
pub async fn process_line(
|
||||||
readline: Result<String, ReadlineError>,
|
line: &str,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
cli_mode: bool,
|
cli_mode: bool,
|
||||||
) -> LineResult {
|
) -> LineResult {
|
||||||
match &readline {
|
if line.trim() == "" {
|
||||||
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
|
LineResult::Success(line.to_string())
|
||||||
|
} else {
|
||||||
Ok(line) => {
|
|
||||||
let line = chomp_newline(line);
|
let line = chomp_newline(line);
|
||||||
ctx.raw_input = line.to_string();
|
ctx.raw_input = line.to_string();
|
||||||
|
|
||||||
@ -865,7 +901,7 @@ pub async fn process_line(
|
|||||||
debug!("=== Parsed ===");
|
debug!("=== Parsed ===");
|
||||||
debug!("{:#?}", result);
|
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);
|
debug!("{:#?}", classified_block);
|
||||||
//println!("{:#?}", pipeline);
|
//println!("{:#?}", pipeline);
|
||||||
@ -982,17 +1018,13 @@ pub async fn process_line(
|
|||||||
InputStream::empty()
|
InputStream::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
classified_block.block.expand_it_usage();
|
|
||||||
|
|
||||||
trace!("{:#?}", classified_block);
|
trace!("{:#?}", classified_block);
|
||||||
let env = ctx.get_env();
|
let env = ctx.get_env();
|
||||||
match run_block(
|
match run_block(
|
||||||
&classified_block.block,
|
&classified_block.block,
|
||||||
ctx,
|
ctx,
|
||||||
input_stream,
|
input_stream,
|
||||||
&Value::nothing(),
|
Scope::from_env(env),
|
||||||
&IndexMap::new(),
|
|
||||||
&env,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -1038,13 +1070,6 @@ pub async fn process_line(
|
|||||||
Err(err) => LineResult::Error(line.to_string(), err),
|
Err(err) => LineResult::Error(line.to_string(), err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
|
||||||
Err(ReadlineError::Eof) => LineResult::Break,
|
|
||||||
Err(err) => {
|
|
||||||
outln!("Error: {:?}", err);
|
|
||||||
LineResult::Break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_err(err: ShellError, source: &Text) {
|
pub fn print_err(err: ShellError, source: &Text) {
|
||||||
@ -1069,7 +1094,7 @@ mod tests {
|
|||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn quickcheck_parse(data: String) -> bool {
|
fn quickcheck_parse(data: String) -> bool {
|
||||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
||||||
let context = crate::context::Context::basic().unwrap();
|
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
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 cal;
|
||||||
pub(crate) mod cd;
|
pub(crate) mod cd;
|
||||||
pub(crate) mod char_;
|
pub(crate) mod char_;
|
||||||
|
pub(crate) mod chart;
|
||||||
pub(crate) mod classified;
|
pub(crate) mod classified;
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard-cli")]
|
||||||
pub(crate) mod clip;
|
pub(crate) mod clip;
|
||||||
pub(crate) mod command;
|
pub(crate) mod command;
|
||||||
pub(crate) mod compact;
|
pub(crate) mod compact;
|
||||||
@ -29,15 +30,19 @@ pub(crate) mod cp;
|
|||||||
pub(crate) mod date;
|
pub(crate) mod date;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
|
pub(crate) mod describe;
|
||||||
pub(crate) mod do_;
|
pub(crate) mod do_;
|
||||||
pub(crate) mod drop;
|
pub(crate) mod drop;
|
||||||
pub(crate) mod du;
|
pub(crate) mod du;
|
||||||
pub(crate) mod each;
|
pub(crate) mod each;
|
||||||
pub(crate) mod echo;
|
pub(crate) mod echo;
|
||||||
|
pub(crate) mod empty;
|
||||||
pub(crate) mod enter;
|
pub(crate) mod enter;
|
||||||
pub(crate) mod every;
|
pub(crate) mod every;
|
||||||
|
pub(crate) mod exec;
|
||||||
pub(crate) mod exit;
|
pub(crate) mod exit;
|
||||||
pub(crate) mod first;
|
pub(crate) mod first;
|
||||||
|
pub(crate) mod flatten;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
pub(crate) mod from;
|
pub(crate) mod from;
|
||||||
pub(crate) mod from_csv;
|
pub(crate) mod from_csv;
|
||||||
@ -64,7 +69,6 @@ pub(crate) mod history;
|
|||||||
pub(crate) mod if_;
|
pub(crate) mod if_;
|
||||||
pub(crate) mod insert;
|
pub(crate) mod insert;
|
||||||
pub(crate) mod into_int;
|
pub(crate) mod into_int;
|
||||||
pub(crate) mod is_empty;
|
|
||||||
pub(crate) mod keep;
|
pub(crate) mod keep;
|
||||||
pub(crate) mod last;
|
pub(crate) mod last;
|
||||||
pub(crate) mod lines;
|
pub(crate) mod lines;
|
||||||
@ -75,6 +79,7 @@ pub(crate) mod mkdir;
|
|||||||
pub(crate) mod move_;
|
pub(crate) mod move_;
|
||||||
pub(crate) mod next;
|
pub(crate) mod next;
|
||||||
pub(crate) mod nth;
|
pub(crate) mod nth;
|
||||||
|
pub(crate) mod nu;
|
||||||
pub(crate) mod open;
|
pub(crate) mod open;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
@ -93,6 +98,7 @@ pub(crate) mod run_alias;
|
|||||||
pub(crate) mod run_external;
|
pub(crate) mod run_external;
|
||||||
pub(crate) mod save;
|
pub(crate) mod save;
|
||||||
pub(crate) mod select;
|
pub(crate) mod select;
|
||||||
|
pub(crate) mod seq;
|
||||||
pub(crate) mod shells;
|
pub(crate) mod shells;
|
||||||
pub(crate) mod shuffle;
|
pub(crate) mod shuffle;
|
||||||
pub(crate) mod size;
|
pub(crate) mod size;
|
||||||
@ -114,12 +120,10 @@ pub(crate) mod to_tsv;
|
|||||||
pub(crate) mod to_url;
|
pub(crate) mod to_url;
|
||||||
pub(crate) mod to_xml;
|
pub(crate) mod to_xml;
|
||||||
pub(crate) mod to_yaml;
|
pub(crate) mod to_yaml;
|
||||||
pub(crate) mod trim;
|
|
||||||
pub(crate) mod uniq;
|
pub(crate) mod uniq;
|
||||||
pub(crate) mod update;
|
pub(crate) mod update;
|
||||||
pub(crate) mod url_;
|
pub(crate) mod url_;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
pub(crate) mod what;
|
|
||||||
pub(crate) mod where_;
|
pub(crate) mod where_;
|
||||||
pub(crate) mod which_;
|
pub(crate) mod which_;
|
||||||
pub(crate) mod with_env;
|
pub(crate) mod with_env;
|
||||||
@ -133,7 +137,7 @@ pub(crate) use command::{
|
|||||||
|
|
||||||
pub(crate) use alias::Alias;
|
pub(crate) use alias::Alias;
|
||||||
pub(crate) use ansi::Ansi;
|
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::Autoenv;
|
||||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||||
@ -141,6 +145,7 @@ pub(crate) use benchmark::Benchmark;
|
|||||||
pub(crate) use build_string::BuildString;
|
pub(crate) use build_string::BuildString;
|
||||||
pub(crate) use cal::Cal;
|
pub(crate) use cal::Cal;
|
||||||
pub(crate) use char_::Char;
|
pub(crate) use char_::Char;
|
||||||
|
pub(crate) use chart::Chart;
|
||||||
pub(crate) use compact::Compact;
|
pub(crate) use compact::Compact;
|
||||||
pub(crate) use config::{
|
pub(crate) use config::{
|
||||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
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 date::{Date, DateFormat, DateNow, DateUTC};
|
||||||
pub(crate) use debug::Debug;
|
pub(crate) use debug::Debug;
|
||||||
pub(crate) use default::Default;
|
pub(crate) use default::Default;
|
||||||
|
pub(crate) use describe::Describe;
|
||||||
pub(crate) use do_::Do;
|
pub(crate) use do_::Do;
|
||||||
pub(crate) use drop::Drop;
|
pub(crate) use drop::Drop;
|
||||||
pub(crate) use du::Du;
|
pub(crate) use du::Du;
|
||||||
pub(crate) use each::Each;
|
pub(crate) use each::Each;
|
||||||
|
pub(crate) use each::EachGroup;
|
||||||
|
pub(crate) use each::EachWindow;
|
||||||
pub(crate) use echo::Echo;
|
pub(crate) use echo::Echo;
|
||||||
|
pub(crate) use empty::Command as Empty;
|
||||||
pub(crate) use if_::If;
|
pub(crate) use if_::If;
|
||||||
pub(crate) use is_empty::IsEmpty;
|
pub(crate) use nu::NuPlugin;
|
||||||
pub(crate) use update::Update;
|
pub(crate) use update::Command as Update;
|
||||||
pub(crate) mod kill;
|
pub(crate) mod kill;
|
||||||
pub(crate) use kill::Kill;
|
pub(crate) use kill::Kill;
|
||||||
pub(crate) mod clear;
|
pub(crate) mod clear;
|
||||||
@ -165,9 +174,11 @@ pub(crate) use clear::Clear;
|
|||||||
pub(crate) mod touch;
|
pub(crate) mod touch;
|
||||||
pub(crate) use enter::Enter;
|
pub(crate) use enter::Enter;
|
||||||
pub(crate) use every::Every;
|
pub(crate) use every::Every;
|
||||||
|
pub(crate) use exec::Exec;
|
||||||
pub(crate) use exit::Exit;
|
pub(crate) use exit::Exit;
|
||||||
pub(crate) use first::First;
|
pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use flatten::Command as Flatten;
|
||||||
|
pub(crate) use format::{FileSize, Format};
|
||||||
pub(crate) use from::From;
|
pub(crate) use from::From;
|
||||||
pub(crate) use from_csv::FromCSV;
|
pub(crate) use from_csv::FromCSV;
|
||||||
pub(crate) use from_eml::FromEML;
|
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::FromYAML;
|
||||||
pub(crate) use from_yaml::FromYML;
|
pub(crate) use from_yaml::FromYML;
|
||||||
pub(crate) use get::Get;
|
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 group_by_date::GroupByDate;
|
||||||
pub(crate) use headers::Headers;
|
pub(crate) use headers::Headers;
|
||||||
pub(crate) use help::Help;
|
pub(crate) use help::Help;
|
||||||
pub(crate) use histogram::Histogram;
|
pub(crate) use histogram::Histogram;
|
||||||
pub(crate) use history::History;
|
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 into_int::IntoInt;
|
||||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||||
pub(crate) use last::Last;
|
pub(crate) use last::Last;
|
||||||
pub(crate) use lines::Lines;
|
pub(crate) use lines::Lines;
|
||||||
pub(crate) use ls::Ls;
|
pub(crate) use ls::Ls;
|
||||||
pub(crate) use math::{
|
pub(crate) use math::{
|
||||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
Math, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian, MathMinimum,
|
||||||
MathStddev, MathSummation, MathVariance,
|
MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||||
};
|
};
|
||||||
pub(crate) use merge::Merge;
|
pub(crate) use merge::Merge;
|
||||||
pub(crate) use mkdir::Mkdir;
|
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 next::Next;
|
||||||
pub(crate) use nth::Nth;
|
pub(crate) use nth::Nth;
|
||||||
pub(crate) use open::Open;
|
pub(crate) use open::Open;
|
||||||
@ -218,7 +229,7 @@ pub(crate) use prev::Previous;
|
|||||||
pub(crate) use pwd::Pwd;
|
pub(crate) use pwd::Pwd;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
pub(crate) use random::RandomUUID;
|
pub(crate) use random::RandomUUID;
|
||||||
pub(crate) use random::{Random, RandomBool, RandomDice};
|
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
||||||
pub(crate) use range::Range;
|
pub(crate) use range::Range;
|
||||||
pub(crate) use reduce::Reduce;
|
pub(crate) use reduce::Reduce;
|
||||||
pub(crate) use reject::Reject;
|
pub(crate) use reject::Reject;
|
||||||
@ -228,6 +239,7 @@ pub(crate) use rm::Remove;
|
|||||||
pub(crate) use run_external::RunExternalCommand;
|
pub(crate) use run_external::RunExternalCommand;
|
||||||
pub(crate) use save::Save;
|
pub(crate) use save::Save;
|
||||||
pub(crate) use select::Select;
|
pub(crate) use select::Select;
|
||||||
|
pub(crate) use seq::Seq;
|
||||||
pub(crate) use shells::Shells;
|
pub(crate) use shells::Shells;
|
||||||
pub(crate) use shuffle::Shuffle;
|
pub(crate) use shuffle::Shuffle;
|
||||||
pub(crate) use size::Size;
|
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 split_by::SplitBy;
|
||||||
pub(crate) use str_::{
|
pub(crate) use str_::{
|
||||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
|
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||||
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
StrReverse, StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring,
|
||||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||||
};
|
};
|
||||||
pub(crate) use table::Table;
|
pub(crate) use table::Table;
|
||||||
pub(crate) use tags::Tags;
|
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_xml::ToXML;
|
||||||
pub(crate) use to_yaml::ToYAML;
|
pub(crate) use to_yaml::ToYAML;
|
||||||
pub(crate) use touch::Touch;
|
pub(crate) use touch::Touch;
|
||||||
pub(crate) use trim::Trim;
|
|
||||||
pub(crate) use uniq::Uniq;
|
pub(crate) use uniq::Uniq;
|
||||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||||
pub(crate) use version::Version;
|
pub(crate) use version::Version;
|
||||||
pub(crate) use what::What;
|
|
||||||
pub(crate) use where_::Where;
|
pub(crate) use where_::Where;
|
||||||
pub(crate) use which_::Which;
|
pub(crate) use which_::Which;
|
||||||
pub(crate) use with_env::WithEnv;
|
pub(crate) use with_env::WithEnv;
|
||||||
pub(crate) use wrap::Wrap;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
|
||||||
|
use deduction_to_signature::DeductionToSignature;
|
||||||
|
use log::trace;
|
||||||
use nu_data::config;
|
use nu_data::config;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_parser::SignatureRegistry;
|
|
||||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
UntaggedValue, Value,
|
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub struct Alias;
|
pub struct Alias;
|
||||||
|
|
||||||
@ -86,7 +86,6 @@ pub async fn alias(
|
|||||||
},
|
},
|
||||||
_ctx,
|
_ctx,
|
||||||
) = args.process(®istry).await?;
|
) = args.process(®istry).await?;
|
||||||
let mut processed_args: Vec<String> = vec![];
|
|
||||||
|
|
||||||
if let Some(true) = save {
|
if let Some(true) = save {
|
||||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
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: 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
|
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") {
|
match result.get_mut("startup") {
|
||||||
Some(startup) => {
|
Some(startup) => {
|
||||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||||
@ -132,10 +131,18 @@ pub async fn alias(
|
|||||||
config::write(&result, &None)?;
|
config::write(&result, &None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in list.iter() {
|
let mut processed_args: Vec<VarDeclaration> = vec![];
|
||||||
if let Ok(string) = item.as_string() {
|
for (_, item) in list.iter().enumerate() {
|
||||||
processed_args.push(format!("${}", string));
|
match item.as_string() {
|
||||||
} else {
|
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(
|
return Err(ShellError::labeled_error(
|
||||||
"Expected a string",
|
"Expected a string",
|
||||||
"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 {
|
if let Some(true) = infer {
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
VarSyntaxShapeDeductor::infer_vars(&processed_args, &block, ®istry)?
|
||||||
CommandAction::AddAlias(
|
|
||||||
name.to_string(),
|
|
||||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
|
||||||
block,
|
|
||||||
),
|
|
||||||
)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
processed_args.into_iter().map(|arg| (arg, None)).collect()
|
||||||
CommandAction::AddAlias(
|
|
||||||
name.to_string(),
|
|
||||||
processed_args
|
|
||||||
.into_iter()
|
|
||||||
.map(|arg| (arg, SyntaxShape::Any))
|
|
||||||
.collect(),
|
|
||||||
block,
|
|
||||||
),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
for pipeline in &block.block {
|
CommandAction::AddAlias(Box::new(signature), 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Alias;
|
use super::Alias;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 ansi_term::Color;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
pub struct Ansi;
|
pub struct Ansi;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AnsiArgs {
|
struct AnsiArgs {
|
||||||
color: Value,
|
color: Value,
|
||||||
|
escape: Option<Tagged<String>>,
|
||||||
|
osc: Option<Tagged<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ansi").required(
|
Signature::build("ansi")
|
||||||
|
.optional(
|
||||||
"color",
|
"color",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"the name of the color to use or 'reset' to reset the color",
|
"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 {
|
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> {
|
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",
|
"\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,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> 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 color_string = color.as_string()?;
|
||||||
|
|
||||||
let ansi_code = str_to_ansi_color(color_string);
|
let ansi_code = str_to_ansi_color(color_string);
|
||||||
|
|
||||||
if let Some(output) = ansi_code {
|
if let Some(output) = ansi_code {
|
||||||
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
|
|||||||
color.tag(),
|
color.tag(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,11 +233,12 @@ pub fn str_to_ansi_color(s: String) -> Option<String> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Ansi;
|
use super::Ansi;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AppendArgs {
|
struct Arguments {
|
||||||
row: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Append;
|
pub struct Command;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Append {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"append"
|
"append"
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ impl WholeStreamCommand for Append {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Append the given row to the table"
|
"Append a row to the table"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -34,16 +34,40 @@ impl WholeStreamCommand for Append {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> 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> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
use nu_protocol::row;
|
||||||
description: "Add something to the end of a list or table",
|
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Add values to the end of the table",
|
||||||
example: "echo [1 2 3] | append 4",
|
example: "echo [1 2 3] | append 4",
|
||||||
result: Some(vec![
|
result: Some(vec![
|
||||||
UntaggedValue::int(1).into(),
|
UntaggedValue::int(1).into(),
|
||||||
@ -51,18 +75,16 @@ impl WholeStreamCommand for Append {
|
|||||||
UntaggedValue::int(3).into(),
|
UntaggedValue::int(3).into(),
|
||||||
UntaggedValue::int(4).into(),
|
UntaggedValue::int(4).into(),
|
||||||
]),
|
]),
|
||||||
}]
|
},
|
||||||
}
|
Example {
|
||||||
}
|
description: "Add row value to the end of the table",
|
||||||
|
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||||
#[cfg(test)]
|
result: Some(vec![
|
||||||
mod tests {
|
row! { "country".into() => Value::from("Ecuador")},
|
||||||
use super::Append;
|
row! { "country".into() => Value::from("New Zealand")},
|
||||||
|
row! { "country".into() => Value::from("USA")},
|
||||||
#[test]
|
]),
|
||||||
fn examples_work_as_expected() {
|
},
|
||||||
use crate::examples::test as test_examples;
|
]
|
||||||
|
|
||||||
test_examples(Append {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
|||||||
external_redirection: ExternalRedirection::Stdout,
|
external_redirection: ExternalRedirection::Stdout,
|
||||||
},
|
},
|
||||||
name_tag: context.name.clone(),
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Command;
|
use super::Command;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Command {})
|
Ok(test_examples(Command {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,25 +10,16 @@ pub enum AutoPivotMode {
|
|||||||
|
|
||||||
impl AutoPivotMode {
|
impl AutoPivotMode {
|
||||||
pub fn is_auto(&self) -> bool {
|
pub fn is_auto(&self) -> bool {
|
||||||
match &self {
|
matches!(self, AutoPivotMode::Auto)
|
||||||
AutoPivotMode::Auto => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_always(&self) -> bool {
|
pub fn is_always(&self) -> bool {
|
||||||
match &self {
|
matches!(self, AutoPivotMode::Always)
|
||||||
AutoPivotMode::Always => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn is_never(&self) -> bool {
|
pub fn is_never(&self) -> bool {
|
||||||
match &self {
|
matches!(self, AutoPivotMode::Never)
|
||||||
AutoPivotMode::Never => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +28,7 @@ pub trait ConfigExtensions: Debug + Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
if let Some(mode) = vars.get("pivot_mode") {
|
if let Some(mode) = vars.get("pivot_mode") {
|
||||||
let mode = match mode.as_string() {
|
let mode = match mode.as_string() {
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
use heim::cpu::time;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
|
||||||
|
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
|
use rand::{
|
||||||
use chrono::prelude::*;
|
distributions::Alphanumeric,
|
||||||
|
prelude::{thread_rng, Rng},
|
||||||
|
};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub struct Benchmark;
|
pub struct Benchmark;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct BenchmarkArgs {
|
struct BenchmarkArgs {
|
||||||
block: Block,
|
block: Block,
|
||||||
|
passthrough: Option<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -22,15 +30,22 @@ impl WholeStreamCommand for Benchmark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("benchmark").required(
|
Signature::build("benchmark")
|
||||||
|
.required(
|
||||||
"block",
|
"block",
|
||||||
SyntaxShape::Block,
|
SyntaxShape::Block,
|
||||||
"the block to run and benchmark",
|
"the block to run and benchmark",
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"passthrough",
|
||||||
|
SyntaxShape::Block,
|
||||||
|
"Display the benchmark results and pass through the block's output",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
|
"Runs a block and returns the time it took to execute it"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -40,6 +55,21 @@ impl WholeStreamCommand for Benchmark {
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
benchmark(args, registry).await
|
benchmark(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Benchmarks a command within a block",
|
||||||
|
example: "benchmark { sleep 500ms }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Benchmarks a command within a block and passes its output through",
|
||||||
|
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
|
||||||
|
result: Some(vec![UntaggedValue::int(45).into()]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn benchmark(
|
async fn benchmark(
|
||||||
@ -48,31 +78,135 @@ async fn benchmark(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let mut context = Context::from_raw(&raw_args, ®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 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(
|
let start_time = Instant::now();
|
||||||
&block,
|
|
||||||
&mut context,
|
|
||||||
input,
|
|
||||||
&scope.it,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let _ = result?.drain_vec().await;
|
#[cfg(feature = "rich-benchmark")]
|
||||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
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();
|
context.clear_errors();
|
||||||
|
|
||||||
let output = Ok(ReturnSuccess::Value(Value {
|
// return basic runtime
|
||||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
#[cfg(not(feature = "rich-benchmark"))]
|
||||||
tag: Tag::from(block.span),
|
{
|
||||||
}));
|
let mut indexmap = IndexMap::with_capacity(1);
|
||||||
|
|
||||||
Ok(OutputStream::from(vec![output]))
|
let real_time = into_big_int(end_time - start_time);
|
||||||
|
indexmap.insert("real time".to_string(), real_time);
|
||||||
|
benchmark_output(indexmap, output, passthrough, &tag, &mut context, scope).await
|
||||||
|
}
|
||||||
|
// return advanced stats
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
if let (Ok(start), Ok(end)) = (start, end) {
|
||||||
|
let mut indexmap = IndexMap::with_capacity(4);
|
||||||
|
|
||||||
|
let real_time = into_big_int(end_time - start_time);
|
||||||
|
indexmap.insert("real time".to_string(), real_time);
|
||||||
|
|
||||||
|
let user_time = into_big_int(end.user() - start.user());
|
||||||
|
indexmap.insert("user time".to_string(), user_time);
|
||||||
|
|
||||||
|
let system_time = into_big_int(end.system() - start.system());
|
||||||
|
indexmap.insert("system time".to_string(), system_time);
|
||||||
|
|
||||||
|
let idle_time = into_big_int(end.idle() - start.idle());
|
||||||
|
indexmap.insert("idle time".to_string(), idle_time);
|
||||||
|
|
||||||
|
benchmark_output(indexmap, output, passthrough, &tag, &mut context, scope).await
|
||||||
|
} else {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Could not retreive CPU time",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn benchmark_output<T, Output>(
|
||||||
|
indexmap: IndexMap<String, BigInt>,
|
||||||
|
block_output: Output,
|
||||||
|
passthrough: Option<Block>,
|
||||||
|
tag: T,
|
||||||
|
context: &mut EvaluationContext,
|
||||||
|
scope: 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cal;
|
use super::Cal;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Cal {})
|
Ok(test_examples(Cal {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,12 @@ impl WholeStreamCommand for Cd {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cd;
|
use super::Cd;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Cd {})
|
Ok(test_examples(Cd {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ pub struct Char;
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CharArgs {
|
struct CharArgs {
|
||||||
name: Tagged<String>,
|
name: Tagged<String>,
|
||||||
|
unicode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ansi").required(
|
Signature::build("ansi")
|
||||||
|
.required(
|
||||||
"character",
|
"character",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"the name of the character to output",
|
"the name of the character to output",
|
||||||
)
|
)
|
||||||
|
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
|
|||||||
UntaggedValue::string("\u{2261}").into(),
|
UntaggedValue::string("\u{2261}").into(),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Output unicode character",
|
||||||
|
example: r#"char -u 1f378"#,
|
||||||
|
result: Some(vec![Value::from("\u{1f378}")]),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,23 +61,43 @@ impl WholeStreamCommand for Char {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> 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);
|
let special_character = str_to_character(&name.item);
|
||||||
|
|
||||||
if let Some(output) = special_character {
|
if let Some(output) = special_character {
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::string(output).into_value(name.tag()),
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::labeled_error(
|
Err(ShellError::labeled_error(
|
||||||
"Unknown character",
|
"error finding named character",
|
||||||
"unknown character",
|
"error finding named character",
|
||||||
name.tag(),
|
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> {
|
fn str_to_character(s: &str) -> Option<String> {
|
||||||
match s {
|
match s {
|
||||||
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
|||||||
"sp" | "space" => Some(" ".into()),
|
"sp" | "space" => Some(" ".into()),
|
||||||
// Unicode names came from https://www.compart.com/en/unicode
|
// Unicode names came from https://www.compart.com/en/unicode
|
||||||
// Private Use Area (U+E000-U+F8FF)
|
// 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()), //
|
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||||
"segment" => Some('\u{e0b0}'.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()), // ⚡
|
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||||
|
|
||||||
|
// Weather symbols
|
||||||
|
"sun" | "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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,11 +159,12 @@ fn str_to_character(s: &str) -> Option<String> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Char;
|
use super::Char;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::expr::run_expression_block;
|
||||||
use crate::commands::classified::internal::run_internal_command;
|
use crate::commands::classified::internal::run_internal_command;
|
||||||
use crate::context::Context;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::stream::InputStream;
|
use crate::stream::InputStream;
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
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;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(crate) async fn run_block(
|
pub(crate) async fn run_block(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||||
for pipeline in &block.block {
|
for pipeline in &block.block {
|
||||||
@ -54,7 +52,7 @@ pub(crate) async fn run_block(
|
|||||||
return Err(e);
|
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();
|
input = InputStream::empty();
|
||||||
}
|
}
|
||||||
@ -64,11 +62,9 @@ pub(crate) async fn run_block(
|
|||||||
|
|
||||||
async fn run_pipeline(
|
async fn run_pipeline(
|
||||||
commands: &Commands,
|
commands: &Commands,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
for item in commands.list.clone() {
|
for item in commands.list.clone() {
|
||||||
input = match item {
|
input = match item {
|
||||||
@ -77,13 +73,13 @@ async fn run_pipeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClassifiedCommand::Expr(expr) => {
|
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::Error(err) => return Err(err.into()),
|
||||||
|
|
||||||
ClassifiedCommand::Internal(left) => {
|
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 futures::stream::once;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::SpannedExpression;
|
use nu_protocol::hir::SpannedExpression;
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Scope;
|
||||||
|
|
||||||
pub(crate) async fn run_expression_block(
|
pub(crate) async fn run_expression_block(
|
||||||
expr: SpannedExpression,
|
expr: SpannedExpression,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
if log_enabled!(log::Level::Trace) {
|
if log_enabled!(log::Level::Trace) {
|
||||||
trace!(target: "nu::run::expr", "->");
|
trace!(target: "nu::run::expr", "->");
|
||||||
@ -21,7 +19,7 @@ pub(crate) async fn run_expression_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let registry = context.registry().clone();
|
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())
|
Ok(once(async { Ok(output) }).to_input_stream())
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
|||||||
use crate::futures::ThreadedReceiver;
|
use crate::futures::ThreadedReceiver;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
@ -13,15 +14,16 @@ use futures_codec::FramedRead;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::hir::Expression;
|
||||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
pub(crate) async fn run_external_command(
|
pub(crate) async fn run_external_command(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
external_redirection: ExternalRedirection,
|
external_redirection: ExternalRedirection,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||||
@ -39,9 +41,9 @@ pub(crate) async fn run_external_command(
|
|||||||
|
|
||||||
async fn run_with_stdin(
|
async fn run_with_stdin(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
external_redirection: ExternalRedirection,
|
external_redirection: ExternalRedirection,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let path = context.shell_manager.path();
|
let path = context.shell_manager.path();
|
||||||
@ -50,9 +52,8 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
let mut command_args = vec![];
|
let mut command_args = vec![];
|
||||||
for arg in command.args.iter() {
|
for arg in command.args.iter() {
|
||||||
let value =
|
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Skip any arguments that don't really exist, treating them as optional
|
// 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
|
// 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 {
|
for t in table {
|
||||||
match &t.value {
|
match &t.value {
|
||||||
UntaggedValue::Primitive(_) => {
|
UntaggedValue::Primitive(_) => {
|
||||||
command_args
|
command_args.push((
|
||||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||||
|
is_literal,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::labeled_error(
|
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();
|
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
|
let process_args = command_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|(arg, _is_literal)| {
|
||||||
let home_dir;
|
let home_dir;
|
||||||
|
|
||||||
#[cfg(feature = "dirs")]
|
#[cfg(feature = "dirs")]
|
||||||
@ -105,8 +108,9 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
if !_is_literal {
|
||||||
add_quotes(&arg)
|
let escaped = escape_double_quotes(&arg);
|
||||||
|
add_double_quotes(&escaped)
|
||||||
} else {
|
} else {
|
||||||
arg.as_ref().to_string()
|
arg.as_ref().to_string()
|
||||||
}
|
}
|
||||||
@ -138,7 +142,7 @@ fn spawn(
|
|||||||
args: &[String],
|
args: &[String],
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
external_redirection: ExternalRedirection,
|
external_redirection: ExternalRedirection,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
|
|
||||||
@ -169,7 +173,7 @@ fn spawn(
|
|||||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||||
|
|
||||||
process.env_clear();
|
process.env_clear();
|
||||||
process.envs(scope.env.iter());
|
process.envs(scope.env());
|
||||||
|
|
||||||
// We want stdout regardless of what
|
// We want stdout regardless of what
|
||||||
// we are doing ($it case or pipe stdin)
|
// we are doing ($it case or pipe stdin)
|
||||||
@ -221,36 +225,19 @@ fn spawn(
|
|||||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||||
UntaggedValue::Primitive(Primitive::String(s))
|
UntaggedValue::Primitive(Primitive::String(s))
|
||||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
if stdin_write.write(s.as_bytes()).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
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(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||||
if let Err(e) = stdin_write.write(b) {
|
if stdin_write.write(b).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
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(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsupported => {
|
unsupported => {
|
||||||
|
println!("Unsupported: {:?}", unsupported);
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
let _ = stdin_write_tx.send(Ok(Value {
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
format!(
|
format!(
|
||||||
@ -496,11 +483,6 @@ where
|
|||||||
shellexpand::tilde_with_context(input, home_dir)
|
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 {
|
fn argument_is_quoted(argument: &str) -> bool {
|
||||||
if argument.len() < 2 {
|
if argument.len() < 2 {
|
||||||
return false;
|
return false;
|
||||||
@ -511,10 +493,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn add_quotes(argument: &str) -> String {
|
fn add_double_quotes(argument: &str) -> String {
|
||||||
format!("\"{}\"", argument)
|
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)]
|
#[allow(unused)]
|
||||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||||
if !argument_is_quoted(argument) {
|
if !argument_is_quoted(argument) {
|
||||||
@ -540,10 +532,10 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
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")]
|
#[cfg(feature = "which")]
|
||||||
use super::{run_external_command, Context, InputStream};
|
use super::{run_external_command, EvaluationContext, InputStream};
|
||||||
|
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
@ -573,13 +565,14 @@ mod tests {
|
|||||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||||
|
|
||||||
let input = InputStream::empty();
|
let input = InputStream::empty();
|
||||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
let mut ctx =
|
||||||
|
EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||||
|
|
||||||
assert!(run_external_command(
|
assert!(run_external_command(
|
||||||
cmd,
|
cmd,
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
input,
|
input,
|
||||||
&Scope::new(),
|
Scope::create(),
|
||||||
ExternalRedirection::Stdout
|
ExternalRedirection::Stdout
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -591,7 +584,7 @@ mod tests {
|
|||||||
// async fn failure_run() -> Result<(), ShellError> {
|
// async fn failure_run() -> Result<(), ShellError> {
|
||||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||||
|
|
||||||
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
// let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||||
// .await?
|
// .await?
|
||||||
// .expect("There was a problem running the external command.");
|
// .expect("There was a problem running the external command.");
|
||||||
@ -619,10 +612,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
fn checks_escape_double_quotes() {
|
||||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -650,9 +643,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -9,22 +9,15 @@ use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue,
|
|||||||
|
|
||||||
pub(crate) async fn run_internal_command(
|
pub(crate) async fn run_internal_command(
|
||||||
command: InternalCommand,
|
command: InternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
if log_enabled!(log::Level::Trace) {
|
if log_enabled!(log::Level::Trace) {
|
||||||
trace!(target: "nu::run::internal", "->");
|
trace!(target: "nu::run::internal", "->");
|
||||||
trace!(target: "nu::run::internal", "{}", command.name);
|
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 objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||||
let internal_command = context.expect_command(&command.name);
|
let internal_command = context.expect_command(&command.name);
|
||||||
|
|
||||||
@ -38,7 +31,7 @@ pub(crate) async fn run_internal_command(
|
|||||||
internal_command?,
|
internal_command?,
|
||||||
Tag::unknown_anchor(command.name_span),
|
Tag::unknown_anchor(command.name_span),
|
||||||
command.args.clone(),
|
command.args.clone(),
|
||||||
&scope,
|
scope.clone(),
|
||||||
objects,
|
objects,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
@ -48,8 +41,6 @@ pub(crate) async fn run_internal_command(
|
|||||||
//let context = Arc::new(context.clone());
|
//let context = Arc::new(context.clone());
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let command = Arc::new(command);
|
let command = Arc::new(command);
|
||||||
let scope = Arc::new(scope);
|
|
||||||
// let scope = scope.clone();
|
|
||||||
|
|
||||||
Ok(InputStream::from_stream(
|
Ok(InputStream::from_stream(
|
||||||
result
|
result
|
||||||
@ -90,7 +81,7 @@ pub(crate) async fn run_internal_command(
|
|||||||
external_redirection: ExternalRedirection::Stdout,
|
external_redirection: ExternalRedirection::Stdout,
|
||||||
},
|
},
|
||||||
name_tag: Tag::unknown_anchor(command.name_span),
|
name_tag: Tag::unknown_anchor(command.name_span),
|
||||||
scope: (&*scope).clone(),
|
scope,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let result = converter
|
let result = converter
|
||||||
@ -194,12 +185,34 @@ pub(crate) async fn run_internal_command(
|
|||||||
));
|
));
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::AddAlias(name, args, block) => {
|
CommandAction::AddAlias(sig, block) => {
|
||||||
context.add_commands(vec![whole_stream_command(
|
context.add_commands(vec![whole_stream_command(
|
||||||
AliasCommand::new(name, args, block),
|
AliasCommand::new(*sig, block),
|
||||||
)]);
|
)]);
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
|
CommandAction::AddPlugins(path) => {
|
||||||
|
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||||
|
Ok(plugins) => {
|
||||||
|
context.add_commands(
|
||||||
|
plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| {
|
||||||
|
!context.is_command_registered(p.name())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
context.error(reason.clone());
|
||||||
|
InputStream::one(
|
||||||
|
UntaggedValue::Error(reason).into_untagged_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
CommandAction::PreviousShell => {
|
CommandAction::PreviousShell => {
|
||||||
context.shell_manager.prev();
|
context.shell_manager.prev();
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
|
@ -130,7 +130,7 @@ async fn run_filter(
|
|||||||
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
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 real_path = Path::new(&path);
|
||||||
let ext = real_path.extension();
|
let ext = real_path.extension();
|
||||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"clears the terminal"
|
"Clears the terminal"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
@ -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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
@ -32,11 +32,18 @@ impl WholeStreamCommand for Clip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Save text to the clipboard",
|
description: "Save text to the clipboard",
|
||||||
example: "echo 'secret value' | clip",
|
example: "echo 'secret value' | clip",
|
||||||
result: None,
|
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;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let string: String = match i.as_string() {
|
let string: String = i.convert_to_string();
|
||||||
Ok(string) => string.to_string(),
|
if string.is_empty() {
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Given non-string data",
|
"Unable to convert to string",
|
||||||
"expected strings from pipeline",
|
"Unable to convert to string",
|
||||||
name,
|
name,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
new_copy_data.push_str(&string);
|
new_copy_data.push_str(&string);
|
||||||
}
|
}
|
||||||
@ -99,11 +104,12 @@ pub async fn clip(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Clip;
|
use super::Clip;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::help::get_help;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::deserializer::ConfigDeserializer;
|
use crate::deserializer::ConfigDeserializer;
|
||||||
use crate::evaluate::evaluate_args::evaluate_args;
|
use crate::evaluate::evaluate_args::evaluate_args;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -17,27 +17,12 @@ use std::sync::atomic::AtomicBool;
|
|||||||
pub struct UnevaluatedCallInfo {
|
pub struct UnevaluatedCallInfo {
|
||||||
pub args: hir::Call,
|
pub args: hir::Call,
|
||||||
pub name_tag: Tag,
|
pub name_tag: Tag,
|
||||||
pub scope: Scope,
|
pub scope: Arc<Scope>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnevaluatedCallInfo {
|
impl UnevaluatedCallInfo {
|
||||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
let args = evaluate_args(&self.args, registry, self.scope.clone()).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?;
|
|
||||||
|
|
||||||
Ok(CallInfo {
|
Ok(CallInfo {
|
||||||
args,
|
args,
|
||||||
@ -115,7 +100,7 @@ impl CommandArgs {
|
|||||||
pub async fn evaluate_once_with_scope(
|
pub async fn evaluate_once_with_scope(
|
||||||
self,
|
self,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||||
let host = self.host.clone();
|
let host = self.host.clone();
|
||||||
let ctrl_c = self.ctrl_c.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)]
|
#[derive(Getters, new)]
|
||||||
#[get = "pub(crate)"]
|
#[get = "pub(crate)"]
|
||||||
pub struct EvaluatedCommandArgs {
|
pub struct EvaluatedCommandArgs {
|
||||||
@ -303,6 +257,11 @@ pub trait WholeStreamCommand: Send + Sync {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands that are not meant to be run by users
|
||||||
|
fn is_internal(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@ -367,74 +326,15 @@ impl Command {
|
|||||||
self.0.is_binary()
|
self.0.is_binary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_internal(&self) -> bool {
|
||||||
|
self.0.is_internal()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||||
&*self.0
|
&*self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||||
Command(Arc::new(command))
|
Command(Arc::new(command))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
@ -37,22 +37,11 @@ impl WholeStreamCommand for Compact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![Example {
|
||||||
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 {
|
|
||||||
description: "Filter out all directory entries having no 'target'",
|
description: "Filter out all directory entries having no 'target'",
|
||||||
example: "ls -la | compact target",
|
example: "ls -la | compact target",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +84,12 @@ pub async fn compact(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Compact;
|
use super::Compact;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct GetArgs {
|
pub struct GetArgs {
|
||||||
get: Tagged<String>,
|
path: ColumnPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -21,7 +20,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("config get").required(
|
Signature::build("config get").required(
|
||||||
"get",
|
"get",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::ColumnPath,
|
||||||
"value to get from the config",
|
"value to get from the config",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -51,17 +50,14 @@ pub async fn get(
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name_span = args.call_info.name_tag.clone();
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
let (GetArgs { get }, _) = args.process(®istry).await?;
|
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
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
// existing config
|
// 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 = crate::commands::get::get_column_path(&path, &result)?;
|
||||||
let value = result
|
|
||||||
.get(&key)
|
|
||||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", get.tag()))?;
|
|
||||||
|
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Value {
|
Value {
|
||||||
@ -75,9 +71,6 @@ pub async fn get(
|
|||||||
|
|
||||||
futures::stream::iter(list).to_output_stream()
|
futures::stream::iter(list).to_output_stream()
|
||||||
}
|
}
|
||||||
x => {
|
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||||
let x = x.clone();
|
|
||||||
OutputStream::one(ReturnSuccess::value(x))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SetArgs {
|
pub struct SetArgs {
|
||||||
key: Tagged<String>,
|
path: ColumnPath,
|
||||||
value: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("config set")
|
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")
|
.required("value", SyntaxShape::Any, "value to use")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,11 +37,28 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Set nonzero_exit_errors to true",
|
Example {
|
||||||
example: "config set nonzero_exit_errors $true",
|
description: "Set auto pivoting",
|
||||||
|
example: "config set pivot_mode always",
|
||||||
result: None,
|
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,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name_span = args.call_info.name_tag.clone();
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
let (SetArgs { key, value }, _) = args.process(®istry).await?;
|
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
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
// existing config
|
// 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(
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
@ -80,11 +80,12 @@ impl WholeStreamCommand for Count {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Count;
|
use super::Count;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
@ -66,11 +66,12 @@ impl WholeStreamCommand for Cpy {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cpy;
|
use super::Cpy;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 {
|
fn usage(&self) -> &str {
|
||||||
"Work with dates."
|
"Apply date function"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -36,11 +36,12 @@ impl WholeStreamCommand for Command {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Command;
|
use super::Command;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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))
|
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))
|
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))
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Debug;
|
use super::Debug;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
@ -83,11 +83,12 @@ async fn default(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Default;
|
use super::Default;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
pub struct What;
|
pub struct Describe;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct WhatArgs {}
|
pub struct DescribeArgs {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for What {
|
impl WholeStreamCommand for Describe {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"describe"
|
"describe"
|
||||||
}
|
}
|
||||||
@ -28,11 +28,11 @@ impl WholeStreamCommand for What {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
what(args, registry).await
|
describe(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn what(
|
pub async fn describe(
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
_registry: &CommandRegistry,
|
_registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
@ -49,12 +49,13 @@ pub async fn what(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::What;
|
use super::Describe;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 registry = registry.clone();
|
||||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||||
|
|
||||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||||
let scope = raw_args.call_info.scope.clone();
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let (
|
let (
|
||||||
DoArgs {
|
DoArgs {
|
||||||
@ -95,15 +95,7 @@ async fn do_(
|
|||||||
|
|
||||||
block.set_redirect(block_redirection);
|
block.set_redirect(block_redirection);
|
||||||
|
|
||||||
let result = run_block(
|
let result = run_block(&block, &mut context, input, scope).await;
|
||||||
&block,
|
|
||||||
&mut context,
|
|
||||||
input,
|
|
||||||
&scope.it,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if ignore_errors {
|
if ignore_errors {
|
||||||
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
||||||
@ -125,11 +117,12 @@ async fn do_(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Do;
|
use super::Do;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
@ -85,11 +85,12 @@ async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Drop;
|
use super::Drop;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Du;
|
use super::Du;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
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(
|
pub async fn process_row(
|
||||||
block: Arc<Block>,
|
block: Arc<Block>,
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
head: Arc<Box<SpannedExpression>>,
|
mut context: Arc<EvaluationContext>,
|
||||||
mut context: Arc<Context>,
|
|
||||||
input: Value,
|
input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let input_clone = input.clone();
|
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()
|
InputStream::empty()
|
||||||
} else {
|
} else {
|
||||||
once(async { Ok(input_clone) }).to_input_stream()
|
once(async { Ok(input_clone) }).to_input_stream()
|
||||||
};
|
};
|
||||||
Ok(run_block(
|
|
||||||
&block,
|
let scope = if !block.params.is_empty() {
|
||||||
Arc::make_mut(&mut context),
|
// FIXME: add check for more than parameter, once that's supported
|
||||||
input_stream,
|
Scope::append_var(scope, block.params[0].clone(), input)
|
||||||
&input,
|
} else {
|
||||||
&scope.vars,
|
scope
|
||||||
&scope.env,
|
};
|
||||||
)
|
|
||||||
|
Ok(
|
||||||
|
run_block(&block, Arc::make_mut(&mut context), input_stream, scope)
|
||||||
.await?
|
.await?
|
||||||
.to_output_stream())
|
.to_output_stream(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||||
@ -118,9 +116,8 @@ async fn each(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
|
||||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||||
let block = Arc::new(each_args.block);
|
let block = Arc::new(each_args.block);
|
||||||
|
|
||||||
@ -130,12 +127,11 @@ async fn each(
|
|||||||
.then(move |input| {
|
.then(move |input| {
|
||||||
let block = block.clone();
|
let block = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let row = make_indexed_item(input.0, input.1);
|
let row = make_indexed_item(input.0, input.1);
|
||||||
|
|
||||||
async {
|
async {
|
||||||
match process_row(block, scope, head, context, row).await {
|
match process_row(block, scope, context, row).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
}
|
}
|
||||||
@ -148,11 +144,10 @@ async fn each(
|
|||||||
.then(move |input| {
|
.then(move |input| {
|
||||||
let block = block.clone();
|
let block = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
async {
|
async {
|
||||||
match process_row(block, scope, head, context, input).await {
|
match process_row(block, scope, context, input).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
}
|
}
|
||||||
@ -166,11 +161,12 @@ async fn each(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Each;
|
use super::Each;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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,
|
end: Primitive,
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
is_end_inclusive: bool,
|
is_end_inclusive: bool,
|
||||||
is_done: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RangeIterator {
|
impl RangeIterator {
|
||||||
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||||
|
let start = match range.from.0.item {
|
||||||
|
Primitive::Nothing => Primitive::Int(0.into()),
|
||||||
|
x => x,
|
||||||
|
};
|
||||||
|
|
||||||
RangeIterator {
|
RangeIterator {
|
||||||
curr: range.from.0.item,
|
curr: start,
|
||||||
end: range.to.0.item,
|
end: range.to.0.item,
|
||||||
tag,
|
tag,
|
||||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||||
is_done: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,14 +102,40 @@ impl RangeIterator {
|
|||||||
impl Iterator for RangeIterator {
|
impl Iterator for RangeIterator {
|
||||||
type Item = Result<ReturnSuccess, ShellError>;
|
type Item = Result<ReturnSuccess, ShellError>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.curr != self.end {
|
let ordering = if self.end == Primitive::Nothing {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
let result =
|
||||||
|
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"Cannot create range",
|
||||||
|
"unsupported range",
|
||||||
|
self.tag.span,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(result) = result {
|
||||||
|
return Some(Err(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = result
|
||||||
|
.expect("Internal error: the error case was already protected, but that failed");
|
||||||
|
|
||||||
|
result.compare()
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
|
||||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||||
|
|
||||||
self.curr = match nu_data::value::compute_values(
|
let next_value = nu_data::value::compute_values(
|
||||||
Operator::Plus,
|
Operator::Plus,
|
||||||
&UntaggedValue::Primitive(self.curr.clone()),
|
&UntaggedValue::Primitive(self.curr.clone()),
|
||||||
&UntaggedValue::int(1),
|
&UntaggedValue::int(1),
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
self.curr = match next_value {
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
UntaggedValue::Primitive(p) => p,
|
UntaggedValue::Primitive(p) => p,
|
||||||
_ => {
|
_ => {
|
||||||
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(ReturnSuccess::value(output))
|
Some(ReturnSuccess::value(output))
|
||||||
} else if self.is_end_inclusive && !self.is_done {
|
|
||||||
self.is_done = true;
|
|
||||||
Some(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: add inclusive/exclusive ranges
|
// TODO: add inclusive/exclusive ranges
|
||||||
None
|
None
|
||||||
@ -138,11 +162,12 @@ impl Iterator for RangeIterator {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Echo;
|
use super::Echo;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::UnevaluatedCallInfo;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::ExternalRedirection;
|
use nu_protocol::hir::ExternalRedirection;
|
||||||
@ -191,11 +191,12 @@ async fn enter(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Enter;
|
use super::Enter;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
@ -93,11 +93,12 @@ async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Every;
|
use super::Every;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::command::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||||
@ -63,11 +63,12 @@ pub async fn exit(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Exit;
|
use super::Exit;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
@ -72,11 +72,12 @@ async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::First;
|
use super::First;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ async fn format_command(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = Arc::new(registry.clone());
|
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 (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
let format_pattern = format(&pattern);
|
let format_pattern = format(&pattern);
|
||||||
@ -83,9 +83,7 @@ async fn format_command(
|
|||||||
let result = evaluate_baseline_expr(
|
let result = evaluate_baseline_expr(
|
||||||
&full_column_path.0,
|
&full_column_path.0,
|
||||||
®istry,
|
®istry,
|
||||||
&value,
|
Scope::append_var(scope.clone(), "$it", value.clone()),
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -153,11 +151,12 @@ fn format(input: &str) -> Vec<FormatCommand> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Format;
|
use super::Format;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::From;
|
use super::From;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(From {})
|
Ok(test_examples(From {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,11 +109,12 @@ async fn from_csv(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromCSV;
|
use super::FromCSV;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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)
|
.delimiter(separator as u8)
|
||||||
.from_reader(s.as_bytes());
|
.from_reader(s.as_bytes());
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
let headers = if headerless {
|
let headers = if headerless {
|
||||||
(1..=reader.headers()?.len())
|
(1..=reader.headers()?.len())
|
||||||
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
|
|||||||
if let Ok(i) = value.parse::<i64>() {
|
if let Ok(i) = value.parse::<i64>() {
|
||||||
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
||||||
} else if let Ok(f) = value.parse::<f64>() {
|
} else if let Ok(f) = value.parse::<f64>() {
|
||||||
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
|
tagged_row.insert_value(
|
||||||
|
header,
|
||||||
|
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
||||||
}
|
}
|
||||||
|
@ -130,11 +130,12 @@ async fn from_eml(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromEML;
|
use super::FromEML;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromIcs;
|
use super::FromIcs;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromIcs {})
|
Ok(test_examples(FromIcs {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,11 +95,12 @@ async fn from_ini(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromINI;
|
use super::FromINI;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 {
|
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
|
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::String(s) => {
|
serde_hjson::Value::String(s) => {
|
||||||
@ -143,11 +144,12 @@ async fn from_json(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromJSON;
|
use super::FromJSON;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromJSON {})
|
Ok(test_examples(FromJSON {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ async fn from_ods(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let span = tag.span;
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
@ -73,7 +74,7 @@ async fn from_ods(
|
|||||||
let value = match cell {
|
let value = match cell {
|
||||||
DataType::Empty => UntaggedValue::nothing(),
|
DataType::Empty => UntaggedValue::nothing(),
|
||||||
DataType::String(s) => UntaggedValue::string(s),
|
DataType::String(s) => UntaggedValue::string(s),
|
||||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||||
DataType::Int(i) => UntaggedValue::int(*i),
|
DataType::Int(i) => UntaggedValue::int(*i),
|
||||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||||
_ => UntaggedValue::nothing(),
|
_ => UntaggedValue::nothing(),
|
||||||
@ -101,11 +102,12 @@ async fn from_ods(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromODS;
|
use super::FromODS;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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))
|
.flat_map(|s| find_indices(*s))
|
||||||
.collect::<Vec<usize>>();
|
.collect::<Vec<usize>>();
|
||||||
|
|
||||||
indices.sort();
|
indices.sort_unstable();
|
||||||
indices.dedup();
|
indices.dedup();
|
||||||
|
|
||||||
let headers: Vec<(String, usize)> = indices
|
let headers: Vec<(String, usize)> = indices
|
||||||
@ -302,7 +302,9 @@ async fn from_ssv(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn owned(x: &str, y: &str) -> (String, String) {
|
fn owned(x: &str, y: &str) -> (String, String) {
|
||||||
(String::from(x), String::from(y))
|
(String::from(x), String::from(y))
|
||||||
}
|
}
|
||||||
@ -504,10 +506,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use super::FromSSV;
|
use super::FromSSV;
|
||||||
use crate::examples::test as test_examples;
|
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 {
|
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||||
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
|
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
|
||||||
toml::Value::Float(n) => UntaggedValue::decimal(*n).into_value(tag),
|
toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
|
||||||
toml::Value::String(s) => {
|
toml::Value::String(s) => {
|
||||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
||||||
}
|
}
|
||||||
@ -100,11 +101,12 @@ pub async fn from_toml(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromTOML;
|
use super::FromTOML;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromTOML {})
|
Ok(test_examples(FromTOML {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,12 @@ async fn from_tsv(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromTSV;
|
use super::FromTSV;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromTSV {})
|
Ok(test_examples(FromTSV {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,11 +64,12 @@ async fn from_url(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromURL;
|
use super::FromURL;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromVcf;
|
use super::FromVcf;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromVcf {})
|
Ok(test_examples(FromVcf {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ async fn from_xlsx(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let span = tag.span;
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (
|
let (
|
||||||
FromXLSXArgs {
|
FromXLSXArgs {
|
||||||
@ -73,7 +74,7 @@ async fn from_xlsx(
|
|||||||
let value = match cell {
|
let value = match cell {
|
||||||
DataType::Empty => UntaggedValue::nothing(),
|
DataType::Empty => UntaggedValue::nothing(),
|
||||||
DataType::String(s) => UntaggedValue::string(s),
|
DataType::String(s) => UntaggedValue::string(s),
|
||||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||||
DataType::Int(i) => UntaggedValue::int(*i),
|
DataType::Int(i) => UntaggedValue::int(*i),
|
||||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||||
_ => UntaggedValue::nothing(),
|
_ => UntaggedValue::nothing(),
|
||||||
@ -101,11 +102,12 @@ async fn from_xlsx(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromXLSX;
|
use super::FromXLSX;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromXLSX {})
|
Ok(test_examples(FromXLSX {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +135,7 @@ async fn from_xml(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use crate::commands::from_xml;
|
use crate::commands::from_xml;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use nu_protocol::{UntaggedValue, Value};
|
use nu_protocol::{UntaggedValue, Value};
|
||||||
@ -304,10 +305,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use super::FromXML;
|
use super::FromXML;
|
||||||
use crate::examples::test as test_examples;
|
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>,
|
tag: impl Into<Tag>,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
let err_not_compatible_number = ShellError::labeled_error(
|
let err_not_compatible_number = ShellError::labeled_error(
|
||||||
"Expected a compatible number",
|
"Expected a compatible number",
|
||||||
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
|
|||||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||||
.into_value(tag)
|
span,
|
||||||
}
|
)
|
||||||
|
.into_value(tag),
|
||||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||||
serde_yaml::Value::Sequence(a) => {
|
serde_yaml::Value::Sequence(a) => {
|
||||||
let result: Result<Vec<Value>, ShellError> = a
|
let result: Result<Vec<Value>, ShellError> = a
|
||||||
@ -171,15 +173,16 @@ async fn from_yaml(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::*;
|
use super::*;
|
||||||
use nu_plugin::row;
|
use nu_protocol::row;
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromYAML {})
|
Ok(test_examples(FromYAML {})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
|
||||||
UnspannedPathMember, UntaggedValue, Value,
|
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::HasFallibleSpan;
|
use nu_source::HasFallibleSpan;
|
||||||
use nu_value_ext::get_data_by_column_path;
|
use nu_value_ext::get_data_by_column_path;
|
||||||
@ -58,17 +58,95 @@ impl WholeStreamCommand for Get {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
pub async fn get(
|
||||||
let fields = path.clone();
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let (GetArgs { rest: column_paths }, mut input) = args.process(®istry).await?;
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
let vec = input.drain_vec().await;
|
||||||
|
|
||||||
get_data_by_column_path(
|
let descs = nu_protocol::merge_descriptors(&vec);
|
||||||
obj,
|
|
||||||
path,
|
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
} else {
|
||||||
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
|
trace!("get {:?}", column_paths);
|
||||||
|
let output_stream = input
|
||||||
|
.map(move |item| {
|
||||||
|
let output = column_paths
|
||||||
|
.iter()
|
||||||
|
.map(move |path| get_output(&item, path))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
futures::stream::iter(output)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream();
|
||||||
|
Ok(output_stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
|
||||||
|
match get_column_path(path, item) {
|
||||||
|
Ok(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||||
|
..
|
||||||
|
}) => vec![],
|
||||||
|
Ok(Value {
|
||||||
|
value: UntaggedValue::Table(rows),
|
||||||
|
..
|
||||||
|
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
|
||||||
|
Ok(other) => vec![ReturnSuccess::value(other)],
|
||||||
|
Err(reason) => vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Error(reason).into_untagged_value(),
|
||||||
|
)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||||
|
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
|
||||||
|
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
|
||||||
|
|
||||||
match &obj_source.value {
|
match &obj_source.value {
|
||||||
UntaggedValue::Table(rows) => match column_path_tried {
|
UntaggedValue::Table(rows) => {
|
||||||
|
return get_column_path_from_table_error(
|
||||||
|
rows,
|
||||||
|
column_path_tried,
|
||||||
|
&path_members_span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
UntaggedValue::Row(columns) => {
|
||||||
|
if let Some(error) = get_column_from_row_error(
|
||||||
|
columns,
|
||||||
|
column_path_tried,
|
||||||
|
&path_members_span,
|
||||||
|
obj_source,
|
||||||
|
) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.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 {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::String(column),
|
unspanned: UnspannedPathMember::String(column),
|
||||||
..
|
..
|
||||||
@ -77,8 +155,8 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
|||||||
|
|
||||||
let suggestions: IndexSet<_> = rows
|
let suggestions: IndexSet<_> = rows
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
.filter_map(|r| did_you_mean(&r, column_path_tried.as_string()))
|
||||||
.map(|s| s[0].1.to_owned())
|
.map(|s| s[0].to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||||
let mut names: Vec<String> = vec![];
|
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() {
|
if names.is_empty() {
|
||||||
return ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
"Appears to contain rows. Try indexing instead.",
|
"Appears to contain rows. Try indexing instead.",
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
return ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
@ -115,8 +193,8 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
|||||||
names.join(", ")
|
names.join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
PathMember {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::Int(idx),
|
unspanned: UnspannedPathMember::Int(idx),
|
||||||
@ -130,41 +208,50 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
|||||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
return ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
"Row not found",
|
"Row not found",
|
||||||
format!("There isn't a row indexed at {}", idx),
|
format!("There isn't a row indexed at {}", idx),
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
secondary_label,
|
secondary_label,
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
UntaggedValue::Row(columns) => match column_path_tried {
|
}
|
||||||
|
|
||||||
|
pub fn get_column_from_row_error(
|
||||||
|
columns: &Dictionary,
|
||||||
|
column_path_tried: &PathMember,
|
||||||
|
path_members_span: &Span,
|
||||||
|
obj_source: &Value,
|
||||||
|
) -> Option<ShellError> {
|
||||||
|
match column_path_tried {
|
||||||
PathMember {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::String(column),
|
unspanned: UnspannedPathMember::String(column),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||||
|
|
||||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.as_string()) {
|
||||||
return ShellError::labeled_error_with_secondary(
|
Some(ShellError::labeled_error_with_secondary(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
format!(
|
format!(
|
||||||
"Perhaps you meant '{}'? Columns available: {}",
|
"Perhaps you meant '{}'? Columns available: {}",
|
||||||
suggestions[0].1,
|
suggestions[0],
|
||||||
&obj_source.data_descriptors().join(", ")
|
&obj_source.data_descriptors().join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PathMember {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::Int(idx),
|
unspanned: UnspannedPathMember::Int(idx),
|
||||||
..
|
..
|
||||||
} => {
|
} => Some(ShellError::labeled_error_with_secondary(
|
||||||
return ShellError::labeled_error_with_secondary(
|
|
||||||
"No rows available",
|
"No rows available",
|
||||||
format!("A row at '{}' can't be indexed.", &idx),
|
format!("A row at '{}' can't be indexed.", &idx),
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
@ -173,91 +260,19 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
|||||||
columns.keys().join(", ")
|
columns.keys().join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
|
||||||
return ShellError::labeled_error(
|
|
||||||
"Unknown column",
|
|
||||||
format!("did you mean '{}'?", suggestions[0].1),
|
|
||||||
column_path_tried.span.since(path_members_span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
error
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Get;
|
use super::Get;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Get {})
|
Ok(test_examples(Get {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::utils::suggestions::suggestions;
|
||||||
use indexmap::indexmap;
|
use indexmap::indexmap;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use nu_value_ext::as_string;
|
use nu_value_ext::as_string;
|
||||||
|
|
||||||
pub struct GroupBy;
|
pub struct Command;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct GroupByArgs {
|
pub struct Arguments {
|
||||||
grouper: Option<Value>,
|
grouper: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for GroupBy {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"group-by"
|
"group-by"
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ impl WholeStreamCommand for GroupBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"create a new table grouped."
|
"Create a new table grouped."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -39,17 +40,45 @@ impl WholeStreamCommand for GroupBy {
|
|||||||
group_by(args, registry).await
|
group_by(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
use nu_data::value::date_naive_from_str as date;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "group items by column named \"type\"",
|
description: "group items by column named \"type\"",
|
||||||
example: r#"ls | group-by type"#,
|
example: r#"ls | group-by type"#,
|
||||||
result: None,
|
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||||
},
|
"File".to_string() => UntaggedValue::Table(vec![
|
||||||
Example {
|
UntaggedValue::row(indexmap! {
|
||||||
description: "blocks can be used for generating a grouping key (same as above)",
|
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||||
example: r#"ls | group-by { get type }"#,
|
"type".to_string() => UntaggedValue::string("File").into(),
|
||||||
result: None,
|
"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 {
|
Example {
|
||||||
description: "you can also group by raw values by leaving out the argument",
|
description: "you can also group by raw values by leaving out the argument",
|
||||||
@ -74,10 +103,26 @@ impl WholeStreamCommand for GroupBy {
|
|||||||
.into()]),
|
.into()]),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "write pipelines for a more involved grouping key",
|
description:
|
||||||
example:
|
"use the block form to generate a grouping key when each row gets processed",
|
||||||
"echo [1 3 1 3 2 1 1] | group-by { echo `({{$it}} - 1) % 3` | calc | str from }",
|
example: "echo [1 3 1 3 2 1 1] | group-by { = ($it - 1) mod 3 }",
|
||||||
result: None,
|
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> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(args.call_info.args.head.clone());
|
let scope = args.call_info.scope.clone();
|
||||||
let scope = Arc::new(args.call_info.scope.clone());
|
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||||
let context = Arc::new(Context::from_raw(&args, ®istry));
|
let (Arguments { grouper }, input) = args.process(®istry).await?;
|
||||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
let values: Vec<Value> = input.collect().await;
|
let values: Vec<Value> = input.collect().await;
|
||||||
let mut keys: Vec<Result<String, ShellError>> = vec![];
|
let mut keys: Vec<Result<String, ShellError>> = vec![];
|
||||||
@ -114,12 +158,9 @@ pub async fn group_by(
|
|||||||
for value in values.iter() {
|
for value in values.iter() {
|
||||||
let run = block.clone();
|
let run = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
match crate::commands::each::process_row(run, scope, head, context, value.clone())
|
match crate::commands::each::process_row(run, scope, context, value.clone()).await {
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(mut s) => {
|
Ok(mut s) => {
|
||||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
s.drain_vec().await;
|
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 values = UntaggedValue::table(&values).into_value(&name);
|
||||||
|
|
||||||
let group_value = match group_strategy {
|
let group_value = match group_strategy {
|
||||||
@ -179,39 +228,14 @@ pub async fn group_by(
|
|||||||
None => as_string(row),
|
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?)))
|
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(
|
pub fn group(
|
||||||
column_name: &Option<Tagged<String>>,
|
column_name: &Option<Tagged<String>>,
|
||||||
values: &Value,
|
values: &Value,
|
||||||
@ -250,9 +274,10 @@ pub fn group(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::group;
|
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_errors::ShellError;
|
||||||
use nu_source::*;
|
use nu_source::*;
|
||||||
|
use nu_test_support::value::{date, int, row, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||||
@ -311,12 +336,4 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::utils::suggestions::suggestions;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::GroupByDate;
|
use super::GroupByDate;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@ -116,11 +116,12 @@ pub async fn headers(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Headers;
|
use super::Headers;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||||
use nu_source::{SpannedItem, Tagged};
|
use nu_source::{SpannedItem, Tagged};
|
||||||
use nu_value_ext::get_data_by_key;
|
use nu_value_ext::ValueExt;
|
||||||
|
|
||||||
pub struct Help;
|
pub struct Help;
|
||||||
|
|
||||||
@ -63,54 +63,115 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
let mut sorted_names = registry.names();
|
let mut sorted_names = registry.names();
|
||||||
sorted_names.sort();
|
sorted_names.sort();
|
||||||
|
|
||||||
Ok(
|
let (mut subcommand_names, command_names) = sorted_names
|
||||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
.into_iter()
|
||||||
// If it's a subcommand, don't list it during the commands list
|
// Internal only commands shouldn't be displayed
|
||||||
if cmd.contains(' ') {
|
.filter(|cmd_name| {
|
||||||
return None;
|
registry
|
||||||
}
|
.get_command(&cmd_name)
|
||||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
.filter(|command| !command.is_internal())
|
||||||
|
.is_some()
|
||||||
|
})
|
||||||
|
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
|
||||||
|
|
||||||
|
fn process_name(
|
||||||
|
dict: &mut TaggedDictBuilder,
|
||||||
|
cmd_name: &str,
|
||||||
|
registry: CommandRegistry,
|
||||||
|
rest: Vec<Tagged<String>>,
|
||||||
|
name: Tag,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
let document_tag = rest[0].tag.clone();
|
let document_tag = rest[0].tag.clone();
|
||||||
let value = command_dict(
|
let value = command_dict(
|
||||||
match registry.get_command(&cmd).ok_or_else(|| {
|
registry.get_command(&cmd_name).ok_or_else(|| {
|
||||||
ShellError::labeled_error(
|
ShellError::labeled_error(
|
||||||
format!("Could not load {}", cmd),
|
format!("Could not load {}", cmd_name),
|
||||||
"could not load command",
|
"could not load command",
|
||||||
document_tag,
|
document_tag,
|
||||||
)
|
)
|
||||||
}) {
|
})?,
|
||||||
Ok(ok) => ok,
|
name,
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
},
|
|
||||||
name.clone(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
short_desc.insert_untagged("name", cmd);
|
dict.insert_untagged("name", cmd_name);
|
||||||
short_desc.insert_untagged(
|
dict.insert_untagged(
|
||||||
"description",
|
"description",
|
||||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
value
|
||||||
|| {
|
.get_data_by_key("usage".spanned_unknown())
|
||||||
|
.ok_or_else(|| {
|
||||||
ShellError::labeled_error(
|
ShellError::labeled_error(
|
||||||
"Expected a usage key",
|
"Expected a usage key",
|
||||||
"expected a 'usage' key",
|
"expected a 'usage' key",
|
||||||
&value.tag,
|
&value.tag,
|
||||||
)
|
)
|
||||||
},
|
})?
|
||||||
) {
|
.as_string()?,
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
}
|
|
||||||
.as_string()
|
|
||||||
{
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
Ok(())
|
||||||
}))
|
}
|
||||||
.to_output_stream(),
|
|
||||||
|
fn make_subcommands_table(
|
||||||
|
subcommand_names: &mut Vec<String>,
|
||||||
|
cmd_name: &str,
|
||||||
|
registry: CommandRegistry,
|
||||||
|
rest: Vec<Tagged<String>>,
|
||||||
|
name: Tag,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
let (matching, not_matching) =
|
||||||
|
subcommand_names.drain(..).partition(|subcommand_name| {
|
||||||
|
subcommand_name.starts_with(&format!("{} ", cmd_name))
|
||||||
|
});
|
||||||
|
*subcommand_names = not_matching;
|
||||||
|
Ok(if !matching.is_empty() {
|
||||||
|
UntaggedValue::table(
|
||||||
|
&(matching
|
||||||
|
.into_iter()
|
||||||
|
.map(|cmd_name: String| -> Result<_, ShellError> {
|
||||||
|
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||||
|
process_name(
|
||||||
|
&mut short_desc,
|
||||||
|
&cmd_name,
|
||||||
|
registry.clone(),
|
||||||
|
rest.clone(),
|
||||||
|
name.clone(),
|
||||||
|
)?;
|
||||||
|
Ok(short_desc.into_value())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?[..]),
|
||||||
)
|
)
|
||||||
|
.into_value(name)
|
||||||
|
} else {
|
||||||
|
UntaggedValue::nothing().into_value(name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let iterator =
|
||||||
|
command_names
|
||||||
|
.into_iter()
|
||||||
|
.map(move |cmd_name| -> Result<_, ShellError> {
|
||||||
|
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||||
|
process_name(
|
||||||
|
&mut short_desc,
|
||||||
|
&cmd_name,
|
||||||
|
registry.clone(),
|
||||||
|
rest.clone(),
|
||||||
|
name.clone(),
|
||||||
|
)?;
|
||||||
|
short_desc.insert_value(
|
||||||
|
"subcommands",
|
||||||
|
make_subcommands_table(
|
||||||
|
&mut subcommand_names,
|
||||||
|
&cmd_name,
|
||||||
|
registry.clone(),
|
||||||
|
rest.clone(),
|
||||||
|
name.clone(),
|
||||||
|
)?,
|
||||||
|
);
|
||||||
|
ReturnSuccess::value(short_desc.into_value())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(iterator).to_output_stream())
|
||||||
} else if rest[0].item == "generate_docs" {
|
} else if rest[0].item == "generate_docs" {
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||||
®istry,
|
®istry,
|
||||||
@ -174,11 +235,12 @@ pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> Str
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Help;
|
use super::Help;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 {
|
nu_data::utils::Operation {
|
||||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||||
splitter: Some(splitter(column_grouper)),
|
splitter: Some(splitter(column_grouper)),
|
||||||
format: None,
|
format: &None,
|
||||||
eval: &evaluate_with,
|
eval: &evaluate_with,
|
||||||
|
reduction: &nu_data::utils::Reduction::Count,
|
||||||
},
|
},
|
||||||
&name,
|
&name,
|
||||||
)?;
|
)?;
|
||||||
@ -123,17 +124,33 @@ pub async fn histogram(
|
|||||||
|
|
||||||
Ok(futures::stream::iter(
|
Ok(futures::stream::iter(
|
||||||
results
|
results
|
||||||
.percentages
|
.data
|
||||||
.table_entries()
|
.table_entries()
|
||||||
.map(move |value| {
|
.cloned()
|
||||||
let values = value.table_entries().cloned().collect::<Vec<_>>();
|
|
||||||
let occurrences = values.len();
|
|
||||||
|
|
||||||
(occurrences, values[occurrences - 1].clone())
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
.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 mut fact = TaggedDictBuilder::new(&name);
|
||||||
let column_value = labels
|
let column_value = labels
|
||||||
.get(idx)
|
.get(idx)
|
||||||
@ -147,19 +164,19 @@ pub async fn histogram(
|
|||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
fact.insert_value(&column.item, column_value);
|
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
|
// Some(2) < the number of digits
|
||||||
// true < group the 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()?
|
.as_string()?
|
||||||
);
|
);
|
||||||
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
|
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
|
||||||
|
|
||||||
let string = std::iter::repeat("*")
|
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)
|
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||||
})? as usize)
|
})? as usize)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
@ -178,11 +195,7 @@ fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, Shell
|
|||||||
Box::new(move |_: usize, value: &Value| {
|
Box::new(move |_: usize, value: &Value| {
|
||||||
let path = by.clone();
|
let path = by.clone();
|
||||||
|
|
||||||
let eval = nu_value_ext::get_data_by_column_path(
|
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||||
value,
|
|
||||||
&path,
|
|
||||||
Box::new(move |(_, _, error)| error),
|
|
||||||
);
|
|
||||||
|
|
||||||
match eval {
|
match eval {
|
||||||
Ok(with_value) => Ok(with_value),
|
Ok(with_value) => Ok(with_value),
|
||||||
@ -214,11 +227,12 @@ fn splitter(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Histogram;
|
use super::Histogram;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Histogram {})
|
Ok(test_examples(Histogram {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::config::NuConfig;
|
use nu_data::config::{Conf, NuConfig};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -9,9 +9,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
const DEFAULT_LOCATION: &str = "history.txt";
|
const DEFAULT_LOCATION: &str = "history.txt";
|
||||||
|
|
||||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||||
let vars = config.vars.lock();
|
|
||||||
|
|
||||||
let default_path = nu_data::config::user_data()
|
let default_path = nu_data::config::user_data()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
p.push(DEFAULT_LOCATION);
|
p.push(DEFAULT_LOCATION);
|
||||||
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||||
|
|
||||||
vars.get("history-path")
|
config
|
||||||
|
.var("history-path")
|
||||||
.map_or(default_path.clone(), |custom_path| {
|
.map_or(default_path.clone(), |custom_path| {
|
||||||
match custom_path.as_string() {
|
match custom_path.as_string() {
|
||||||
Ok(path) => PathBuf::from(path),
|
Ok(path) => PathBuf::from(path),
|
||||||
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
let config = NuConfig::new();
|
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||||
let tag = args.call_info.name_tag;
|
let tag = args.call_info.name_tag;
|
||||||
let path = history_path(&config);
|
let path = history_path(&config);
|
||||||
let file = File::open(path);
|
let file = File::open(path);
|
||||||
@ -80,11 +79,12 @@ fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStrea
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::History;
|
use super::History;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{
|
||||||
|
hir::Block, hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct If;
|
pub struct If;
|
||||||
|
|
||||||
@ -72,9 +74,9 @@ async fn if_command(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = Arc::new(registry.clone());
|
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 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 (
|
let (
|
||||||
IfArgs {
|
IfArgs {
|
||||||
@ -119,14 +121,12 @@ async fn if_command(
|
|||||||
let then_case = then_case.clone();
|
let then_case = then_case.clone();
|
||||||
let else_case = else_case.clone();
|
let else_case = else_case.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = scope.clone();
|
let scope = Scope::append_var(scope.clone(), "$it", input);
|
||||||
let mut context = context.clone();
|
let mut context = context.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
//FIXME: should we use the scope that's brought in as well?
|
//FIXME: should we use the scope that's brought in as well?
|
||||||
let condition =
|
let condition = evaluate_baseline_expr(&condition, &*registry, scope.clone()).await;
|
||||||
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match condition {
|
match condition {
|
||||||
Ok(condition) => match condition.as_bool() {
|
Ok(condition) => match condition.as_bool() {
|
||||||
@ -136,9 +136,7 @@ async fn if_command(
|
|||||||
&then_case,
|
&then_case,
|
||||||
Arc::make_mut(&mut context),
|
Arc::make_mut(&mut context),
|
||||||
InputStream::empty(),
|
InputStream::empty(),
|
||||||
&input,
|
scope,
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -151,9 +149,7 @@ async fn if_command(
|
|||||||
&else_case,
|
&else_case,
|
||||||
Arc::make_mut(&mut context),
|
Arc::make_mut(&mut context),
|
||||||
InputStream::empty(),
|
InputStream::empty(),
|
||||||
&input,
|
scope,
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -178,11 +174,12 @@ async fn if_command(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::If;
|
use super::If;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -9,16 +9,18 @@ use nu_protocol::{
|
|||||||
use nu_value_ext::ValueExt;
|
use nu_value_ext::ValueExt;
|
||||||
|
|
||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
pub struct Insert;
|
use indexmap::indexmap;
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct InsertArgs {
|
pub struct Arguments {
|
||||||
column: ColumnPath,
|
column: ColumnPath,
|
||||||
value: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Insert {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"insert"
|
"insert"
|
||||||
}
|
}
|
||||||
@ -44,11 +46,33 @@ impl WholeStreamCommand for Insert {
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
insert(args, registry).await
|
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(
|
async fn process_row(
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
mut context: Arc<Context>,
|
mut context: Arc<EvaluationContext>,
|
||||||
input: Value,
|
input: Value,
|
||||||
mut value: Arc<Value>,
|
mut value: Arc<Value>,
|
||||||
field: Arc<ColumnPath>,
|
field: Arc<ColumnPath>,
|
||||||
@ -63,15 +87,9 @@ async fn process_row(
|
|||||||
let for_block = input.clone();
|
let for_block = input.clone();
|
||||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||||
|
|
||||||
let result = run_block(
|
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||||
&block,
|
|
||||||
Arc::make_mut(&mut context),
|
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
||||||
input_stream,
|
|
||||||
&input,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(mut stream) => {
|
Ok(mut stream) => {
|
||||||
@ -85,13 +103,16 @@ async fn process_row(
|
|||||||
let result = if values.len() == 1 {
|
let result = if values.len() == 1 {
|
||||||
let value = values
|
let value = values
|
||||||
.get(0)
|
.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() {
|
} else if values.is_empty() {
|
||||||
UntaggedValue::nothing().into_untagged_value()
|
UntaggedValue::nothing().into_value(&input.tag)
|
||||||
} else {
|
} else {
|
||||||
UntaggedValue::table(&values).into_untagged_value()
|
UntaggedValue::table(&values).into_value(&input.tag)
|
||||||
};
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
@ -118,7 +139,11 @@ async fn process_row(
|
|||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
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)),
|
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
},
|
},
|
||||||
@ -135,9 +160,9 @@ async fn insert(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
let (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
let (Arguments { column, value }, input) = raw_args.process(®istry).await?;
|
||||||
let value = Arc::new(value);
|
let value = Arc::new(value);
|
||||||
let column = Arc::new(column);
|
let column = Arc::new(column);
|
||||||
|
|
||||||
@ -158,15 +183,3 @@ async fn insert(
|
|||||||
.flatten()
|
.flatten()
|
||||||
.to_output_stream())
|
.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> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Convert filesize to integer",
|
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()]),
|
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@ -78,11 +78,12 @@ async fn into_int(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::IntoInt;
|
use super::IntoInt;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
@ -74,11 +74,12 @@ async fn keep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Command;
|
use super::Command;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 crate::prelude::*;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
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;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = Arc::new(registry.clone());
|
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 call_info = args.evaluate_once(®istry).await?;
|
||||||
|
|
||||||
@ -85,19 +85,11 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
.take_while(move |item| {
|
.take_while(move |item| {
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = scope.clone();
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
let item = item.clone();
|
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let result = evaluate_baseline_expr(
|
let result = evaluate_baseline_expr(&*condition, ®istry, scope).await;
|
||||||
&*condition,
|
|
||||||
®istry,
|
|
||||||
&item,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
trace!("RESULT = {:?}", result);
|
trace!("RESULT = {:?}", result);
|
||||||
|
|
||||||
!matches!(result, Ok(ref v) if v.is_true())
|
!matches!(result, Ok(ref v) if v.is_true())
|
||||||
@ -109,12 +101,13 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::SubCommand;
|
use super::SubCommand;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 crate::prelude::*;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
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;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = Arc::new(registry.clone());
|
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 call_info = args.evaluate_once(®istry).await?;
|
||||||
|
|
||||||
let block = call_info.args.expect_nth(0)?.clone();
|
let block = call_info.args.expect_nth(0)?.clone();
|
||||||
@ -84,20 +84,11 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
.take_while(move |item| {
|
.take_while(move |item| {
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = scope.clone();
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
let item = item.clone();
|
|
||||||
|
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let result = evaluate_baseline_expr(
|
let result = evaluate_baseline_expr(&*condition, ®istry, scope).await;
|
||||||
&*condition,
|
|
||||||
®istry,
|
|
||||||
&item,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
trace!("RESULT = {:?}", result);
|
trace!("RESULT = {:?}", result);
|
||||||
|
|
||||||
matches!(result, Ok(ref v) if v.is_true())
|
matches!(result, Ok(ref v) if v.is_true())
|
||||||
@ -109,12 +100,13 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::SubCommand;
|
use super::SubCommand;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
@ -121,11 +121,12 @@ async fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Kill;
|
use super::Kill;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
@ -83,11 +83,12 @@ async fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Last;
|
use super::Last;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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 mut leftover_string = leftover_string.lock();
|
||||||
|
|
||||||
let st = (&*leftover_string).clone() + &st;
|
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> = st.lines().map(|x| x.to_string()).collect();
|
let mut lines: Vec<String> = lo_lines.chain(st_lines).collect();
|
||||||
|
|
||||||
leftover_string.clear();
|
leftover_string.clear();
|
||||||
|
|
||||||
@ -125,11 +125,12 @@ async fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Lines;
|
use super::Lines;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Lines {})
|
Ok(test_examples(Lines {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,11 +89,12 @@ impl WholeStreamCommand for Ls {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Ls;
|
use super::Ls;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Ls {})
|
Ok(test_examples(Ls {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,11 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Get the average of a list of numbers",
|
description: "Get the average of a list of numbers",
|
||||||
example: "echo [-50 100.0 25] | math avg",
|
example: "echo [-50 100.0 25] | math avg",
|
||||||
result: Some(vec![UntaggedValue::decimal(25).into()]),
|
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||||
|
25.0,
|
||||||
|
Span::unknown(),
|
||||||
|
)
|
||||||
|
.into()]),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +141,28 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Duration(duration)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let left = UntaggedValue::from(Primitive::Duration(duration));
|
||||||
|
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Duration(result))) => {
|
||||||
|
Ok(UntaggedValue::duration(result).into_value(name))
|
||||||
|
}
|
||||||
|
Ok(_) => Err(ShellError::labeled_error(
|
||||||
|
"could not calculate average of non-integer or unrelated types",
|
||||||
|
"source",
|
||||||
|
name,
|
||||||
|
)),
|
||||||
|
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||||
|
left_type.spanned(name.span),
|
||||||
|
right_type.spanned(name.span),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Primitive(other),
|
value: UntaggedValue::Primitive(other),
|
||||||
..
|
..
|
||||||
@ -162,12 +188,13 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::SubCommand;
|
use super::SubCommand;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
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