forked from extern/nushell
Compare commits
215 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
47c5346934 | |||
882cf74137 | |||
57a26bbd42 | |||
f9acb7a7a5 | |||
569345e1d4 | |||
adbbcafd30 | |||
860c2a606d | |||
6b5d96337e | |||
f54cf8a096 | |||
b5d591bb09 | |||
60ce497edc | |||
dd4351e2b7 | |||
3f443f40d0 | |||
8192360b20 | |||
889b67ca92 | |||
8d1cecf643 | |||
188d33b306 | |||
965e07d8cc | |||
d6b6b1df38 | |||
c897ac6e1e | |||
df691c6c91 | |||
abc05ece21 | |||
6f69ae8707 | |||
84a6010f71 | |||
634bb688c1 | |||
c14b209276 | |||
6535ae3d6e | |||
0390ec97f4 | |||
e3c4d82798 | |||
ee71a35786 | |||
11ea5e61fc | |||
02763b47f7 | |||
4828a7cd29 | |||
728852c750 | |||
26cec83b63 | |||
4724b3c570 | |||
303a9defd3 | |||
a64270829e | |||
8f5df89a78 | |||
39f402c8cc | |||
7702d683c7 | |||
766533aafb | |||
781e423a97 | |||
6e3a827e32 | |||
6685f74e03 | |||
034c33c2b5 | |||
08d1be79fc | |||
c563b7862e | |||
a64cfb6285 | |||
f078aacc25 | |||
d859bff877 | |||
de5cd4ec23 | |||
48850becd8 | |||
2ea42f296c | |||
eb2ba470c7 | |||
a951edd0d5 | |||
d65a38dd41 | |||
8f568f4fc5 | |||
ee26590011 | |||
11352f87f0 | |||
9f85b10fcb | |||
0dd1403a69 | |||
cb4527fc0d | |||
ad395944ef | |||
6126209f57 | |||
43e061f8c6 | |||
738541f727 | |||
1d5518a214 | |||
57101d5022 | |||
f6ff6ab6e4 | |||
a224cd38ab | |||
e292bb46bb | |||
05aca1c157 | |||
8d269f62dd | |||
b1a946f0dc | |||
c59f860b48 | |||
c6588c661a | |||
8fe269a3d8 | |||
8f00713ad2 | |||
d1d98a897a | |||
3dc95ef765 | |||
84da815b22 | |||
371a951668 | |||
baf84f05d9 | |||
da4d24d082 | |||
22519c9083 | |||
1601aa2f49 | |||
88555860f3 | |||
015d2ee050 | |||
dd7ee1808a | |||
0db4180cea | |||
8ff15c46c1 | |||
48cfc9b598 | |||
87d71604ad | |||
e372e7c448 | |||
0194dee3a6 | |||
cc3c10867c | |||
3c18169f63 | |||
43e9c89125 | |||
2ad07912d9 | |||
51ad019495 | |||
9264325e57 | |||
901157341b | |||
eb766b80c1 | |||
f0dbffd761 | |||
f14c0df582 | |||
362bb1bea3 | |||
724b177c97 | |||
50343f2d6a | |||
3122525b96 | |||
8232c6f185 | |||
6202705eb6 | |||
e1c5940b04 | |||
7f35bfc005 | |||
c48c092125 | |||
028fc9b9cd | |||
eeb9b4edcb | |||
3a7869b422 | |||
c48ea46c4f | |||
f33da33626 | |||
a88f5c7ae7 | |||
cda53b6cda | |||
ee734873ba | |||
9fb6f5cd09 | |||
4ef15b5f80 | |||
ba81278ffd | |||
10fbed3808 | |||
16cfc36aec | |||
aca7f71737 | |||
3282a509a9 | |||
878b748a41 | |||
18a4505b9b | |||
26e77a4b05 | |||
37f10cf273 | |||
5e0a9aecaa | |||
7e2c627044 | |||
4347339e9a | |||
e66a8258ec | |||
e4b42b54ad | |||
de18b9ca2c | |||
a77f0f7b41 | |||
6b31a006b8 | |||
2db4fe83d8 | |||
55a2f284d9 | |||
2d3b1e090a | |||
ed0c1038e3 | |||
0c20282200 | |||
e71f44d26f | |||
e3d7e46855 | |||
9b35aae5e8 | |||
7e9f87c57f | |||
5d17b72852 | |||
6b4634b293 |
@ -9,6 +9,12 @@ strategy:
|
||||
linux-minimal:
|
||||
image: ubuntu-18.04
|
||||
style: 'minimal'
|
||||
linux-extra:
|
||||
image: ubuntu-18.04
|
||||
style: 'extra'
|
||||
linux-wasm:
|
||||
image: ubuntu-18.04
|
||||
style: 'wasm'
|
||||
macos-stable:
|
||||
image: macos-10.14
|
||||
style: 'unflagged'
|
||||
@ -37,6 +43,7 @@ steps:
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
sudo npm install -g wasm-pack
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
@ -49,21 +56,27 @@ steps:
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: cd samples/wasm && wasm-pack build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
||||
|
@ -1 +0,0 @@
|
||||
[build]
|
@ -73,7 +73,7 @@ workflows:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v.*/
|
||||
only: /^\d+\.\d+\.\d+$/
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:latest
|
||||
- run: docker pull quay.io/nushell/nu-base:latest
|
||||
|
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -112,7 +112,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -129,7 +129,7 @@ jobs:
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_stable_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
|
@ -25,38 +25,38 @@ cargo build
|
||||
|
||||
### Useful Commands
|
||||
|
||||
Build and run Nushell:
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
Run Clippy on Nushell:
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests for a specific command
|
||||
- Run all tests for a specific command
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
Check to see if there are code formatting issues
|
||||
- Check to see if there are code formatting issues
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
Format the code in the project
|
||||
- Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
2664
Cargo.lock
generated
2664
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
119
Cargo.toml
119
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.17.0"
|
||||
version = "0.20.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,47 +18,48 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.17.0", path = "./crates/nu-cli"}
|
||||
nu-errors = {version = "0.17.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.17.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.17.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.17.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.17.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.17.0", path = "./crates/nu-value-ext"}
|
||||
nu_plugin_binaryview = {version = "0.17.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.17.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.17.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.17.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.17.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.17.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.17.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_start = {version = "0.17.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.17.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.17.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.17.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.17.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
crossterm = {version = "0.17.5", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
|
||||
crossterm = {version = "0.17", optional = true}
|
||||
semver = {version = "0.10.0", optional = true}
|
||||
syntect = {version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
|
||||
url = {version = "2.1.1", optional = true}
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
clap = "2.33.3"
|
||||
ctrlc = "3.1.6"
|
||||
dunce = "1.0.1"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
starship = "0.43.0"
|
||||
quick-xml = "0.18.1"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.17.0", path = "./crates/nu-test-support"}
|
||||
nu-test-support = {version = "0.20.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = {version = "0.17.0", path = "./crates/nu-build"}
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
@ -74,14 +75,20 @@ default = [
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"rich-benchmark",
|
||||
]
|
||||
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
||||
stable = ["default"]
|
||||
|
||||
# Default
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
textview = ["crossterm", "url", "nu_plugin_textview"]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
@ -89,6 +96,7 @@ bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
@ -96,10 +104,11 @@ tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
@ -128,37 +137,43 @@ name = "nu_plugin_core_sys"
|
||||
path = "src/plugins/nu_plugin_core_sys.rs"
|
||||
required-features = ["sys"]
|
||||
|
||||
# Stable plugins
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_fetch"
|
||||
path = "src/plugins/nu_plugin_stable_fetch.rs"
|
||||
name = "nu_plugin_core_fetch"
|
||||
path = "src/plugins/nu_plugin_core_fetch.rs"
|
||||
required-features = ["fetch"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_binaryview"
|
||||
path = "src/plugins/nu_plugin_stable_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_match"
|
||||
path = "src/plugins/nu_plugin_stable_match.rs"
|
||||
name = "nu_plugin_core_match"
|
||||
path = "src/plugins/nu_plugin_core_match.rs"
|
||||
required-features = ["match"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_post"
|
||||
path = "src/plugins/nu_plugin_stable_post.rs"
|
||||
name = "nu_plugin_core_post"
|
||||
path = "src/plugins/nu_plugin_core_post.rs"
|
||||
required-features = ["post"]
|
||||
|
||||
# Extra plugins
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_tree"
|
||||
path = "src/plugins/nu_plugin_stable_tree.rs"
|
||||
name = "nu_plugin_extra_binaryview"
|
||||
path = "src/plugins/nu_plugin_extra_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_start"
|
||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
14
README.md
14
README.md
@ -51,12 +51,12 @@ To build Nu, you will need to use the **latest stable (1.41 or later)** version
|
||||
Required dependencies:
|
||||
|
||||
* pkg-config and libssl (only needed on Linux)
|
||||
* on Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
* On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
* To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
* on Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
* On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
@ -67,7 +67,7 @@ cargo install nu
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```bash
|
||||
cargo build --workspace --features=stable
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
### Docker
|
||||
@ -194,7 +194,7 @@ For example, you can load a .toml file as structured data and explore it:
|
||||
> open Cargo.toml
|
||||
────────────────────┬───────────────────────────
|
||||
bin │ [table 18 rows]
|
||||
build-dependencies │ [row nu-build serde toml]
|
||||
build-dependencies │ [row serde toml]
|
||||
dependencies │ [row 29 columns]
|
||||
dev-dependencies │ [row nu-test-support]
|
||||
features │ [row 19 columns]
|
||||
@ -236,11 +236,11 @@ Here we use the variable `$it` to refer to the value being piped to the external
|
||||
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
|
||||
|
||||
To set one of these variables, you can use `config --set`. For example:
|
||||
To set one of these variables, you can use `config set`. For example:
|
||||
|
||||
```shell
|
||||
> config --set [edit_mode "vi"]
|
||||
> config --set [path $nu.path]
|
||||
> config set edit_mode "vi"
|
||||
> config set path $nu.path
|
||||
```
|
||||
|
||||
### Shells
|
||||
|
Binary file not shown.
Binary file not shown.
3
build.rs
3
build.rs
@ -1,3 +0,0 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
nu_build::build()
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "Core build system for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-build"
|
||||
version = "0.17.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
serde_json = "1.0.55"
|
||||
toml = "0.5.6"
|
@ -1,80 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref WORKSPACES: Mutex<BTreeMap<String, &'static Path>> = Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
|
||||
|
||||
fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
|
||||
let mut workspaces = WORKSPACES.lock()?;
|
||||
if let Some(rv) = workspaces.get(manifest_dir) {
|
||||
Ok(Some(rv))
|
||||
} else {
|
||||
#[derive(Deserialize)]
|
||||
struct Manifest {
|
||||
workspace_root: String,
|
||||
}
|
||||
let output = std::process::Command::new(env!("CARGO"))
|
||||
.arg("metadata")
|
||||
.arg("--format-version=1")
|
||||
.current_dir(manifest_dir)
|
||||
.output()?;
|
||||
let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
|
||||
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
|
||||
workspaces.insert(manifest_dir.to_string(), path.as_path());
|
||||
Ok(workspaces.get(manifest_dir).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Feature {
|
||||
#[allow(unused)]
|
||||
description: String,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = env::var("CARGO_MANIFEST_DIR")?;
|
||||
|
||||
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
|
||||
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
|
||||
.map(|s| s.split(',').map(|s| s.to_string()).collect())
|
||||
.unwrap_or_else(|_| HashSet::new());
|
||||
|
||||
if all_on && !flags.is_empty() {
|
||||
println!(
|
||||
"cargo:warning=Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
|
||||
);
|
||||
}
|
||||
|
||||
let workspace = match get_cargo_workspace(&input)? {
|
||||
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
|
||||
None => return Ok(()),
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
let path = Path::new(&workspace).join("features.toml");
|
||||
|
||||
// If the crate is being downloaded from crates.io, it won't have a features.toml, and that's ok
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
|
||||
|
||||
for (key, value) in toml.iter() {
|
||||
if value.enabled || all_on || flags.contains(key) {
|
||||
println!("cargo:rustc-cfg={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -4,97 +4,102 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.17.0"
|
||||
version = "0.20.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = {version = "0.17.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.17.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.17.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.17.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.17.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.17.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.17.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.17.0", path = "../nu-value-ext"}
|
||||
nu-data = {version = "0.20.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.20.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.36"
|
||||
async-trait = "0.1.40"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
chrono = {version = "0.4.11", features = ["serde"]}
|
||||
clap = "2.33.1"
|
||||
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "0.5.6"
|
||||
calamine = "0.16.1"
|
||||
chrono = {version = "0.4.15", features = ["serde"]}
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1"
|
||||
ctrlc = {version = "3.1.4", optional = true}
|
||||
csv = "1.1.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "2.0.2", optional = true}
|
||||
dirs = {version = "2.0.2", optional = true}
|
||||
directories = {version = "3.0.1", optional = true}
|
||||
dirs = {version = "3.0.1", optional = true}
|
||||
dtparse = "1.1.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
filesize = "0.2.0"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
fs_extra = "1.2.0"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
futures-util = "0.3.5"
|
||||
futures_codec = "0.4"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = {version = "0.13.6", default_features = false, optional = true}
|
||||
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
heim = {version = "0.1.0-beta.3", optional = true}
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ical = "0.6.0"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.4.0", features = ["serde-1"]}
|
||||
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
natural = "0.5.0"
|
||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.11"
|
||||
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.12"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
ptree = {version = "0.2", optional = true}
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = {version = "0.3.0", optional = true}
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
rand = "0.7.3"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.13.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = {version = "6.3.0", optional = true}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0.57"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.5.2", optional = true}
|
||||
term = {version = "0.6.1", optional = true}
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.1"
|
||||
unicode-segmentation = "1.6.0"
|
||||
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.1", optional = true}
|
||||
which = {version = "4.0.2", optional = true}
|
||||
zip = {version = "0.5.7", optional = true}
|
||||
|
||||
clipboard = {version = "0.5", optional = true}
|
||||
encoding_rs = "0.8.23"
|
||||
rayon = "1.3.1"
|
||||
starship = {version = "0.43.0", optional = true}
|
||||
trash = {version = "1.0.1", optional = true}
|
||||
Inflector = "0.11"
|
||||
clipboard = {version = "0.5.0", optional = true}
|
||||
encoding_rs = "0.8.24"
|
||||
quick-xml = "0.18.1"
|
||||
rayon = "1.4.0"
|
||||
trash = {version = "1.1.1", optional = true}
|
||||
url = "2.1.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
@ -106,17 +111,18 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.23.1"
|
||||
version = "0.24.0"
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = {version = "0.17.0", path = "../nu-build"}
|
||||
git2 = {version = "0.13.11", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["clipboard"]
|
||||
rich-benchmark = ["heim"]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
trash-support = ["trash"]
|
||||
|
BIN
crates/nu-cli/assets/228_themes.zip
Normal file
BIN
crates/nu-cli/assets/228_themes.zip
Normal file
Binary file not shown.
36
crates/nu-cli/build.rs
Normal file
36
crates/nu-cli/build.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::path::Path;
|
||||
use std::{env, fs, io};
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
let out_dir = env::var_os("OUT_DIR").expect(
|
||||
"\
|
||||
OUT_DIR environment variable not found. \
|
||||
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
|
||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
||||
");
|
||||
|
||||
let latest_commit_hash = latest_commit_hash(env::current_dir()?).unwrap_or_default();
|
||||
|
||||
let commit_hash_path = Path::new(&out_dir).join("git_commit_hash");
|
||||
fs::write(commit_hash_path, latest_commit_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn latest_commit_hash<P: AsRef<Path>>(dir: P) -> Result<String, Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "git2")]
|
||||
{
|
||||
use git2::Repository;
|
||||
let dir = dir.as_ref();
|
||||
Ok(Repository::discover(dir)?
|
||||
.head()?
|
||||
.peel_to_commit()?
|
||||
.id()
|
||||
.to_string())
|
||||
}
|
||||
#[cfg(not(feature = "git2"))]
|
||||
{
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
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()
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod compact;
|
||||
@ -36,6 +36,7 @@ pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod format;
|
||||
@ -63,6 +64,7 @@ pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
@ -74,15 +76,17 @@ pub(crate) mod mkdir;
|
||||
pub(crate) mod move_;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod nu;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod pivot;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
pub(crate) mod random;
|
||||
pub(crate) mod range;
|
||||
pub(crate) mod reduce;
|
||||
pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
@ -95,6 +99,7 @@ pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod sleep;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
@ -109,10 +114,11 @@ pub(crate) mod to_md;
|
||||
pub(crate) mod to_toml;
|
||||
pub(crate) mod to_tsv;
|
||||
pub(crate) mod to_url;
|
||||
pub(crate) mod to_xml;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod trim;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod url_;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
@ -142,16 +148,19 @@ pub(crate) use config::{
|
||||
};
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::Date;
|
||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use each::EachGroup;
|
||||
pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
@ -160,6 +169,7 @@ pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use enter::Enter;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
@ -187,13 +197,14 @@ pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use into_int::IntoInt;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
|
||||
MathSummation, MathVariance,
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||
MathStddev, MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
@ -202,14 +213,19 @@ pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use path::{
|
||||
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
|
||||
PathType,
|
||||
};
|
||||
pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice};
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
||||
pub(crate) use range::Range;
|
||||
pub(crate) use reduce::Reduce;
|
||||
pub(crate) use reject::Reject;
|
||||
pub(crate) use rename::Rename;
|
||||
pub(crate) use reverse::Reverse;
|
||||
@ -221,12 +237,15 @@ pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
|
||||
pub(crate) use sleep::Sleep;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrFrom, StrLength, StrReverse,
|
||||
StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
|
||||
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
@ -238,10 +257,11 @@ pub(crate) use to_md::ToMarkdown;
|
||||
pub(crate) use to_toml::ToTOML;
|
||||
pub(crate) use to_tsv::ToTSV;
|
||||
pub(crate) use to_url::ToURL;
|
||||
pub(crate) use to_xml::ToXML;
|
||||
pub(crate) use to_yaml::ToYAML;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use trim::Trim;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
pub(crate) use what::What;
|
||||
pub(crate) use where_::Where;
|
||||
|
@ -1,12 +1,16 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
@ -15,6 +19,7 @@ pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
pub infer: Option<bool>,
|
||||
pub save: Option<bool>,
|
||||
}
|
||||
|
||||
@ -33,6 +38,7 @@ impl WholeStreamCommand for Alias {
|
||||
SyntaxShape::Block,
|
||||
"the block to run as the body of the alias",
|
||||
)
|
||||
.switch("infer", "infer argument types (experimental)", Some('i'))
|
||||
.switch("save", "save the alias to your config", Some('s'))
|
||||
}
|
||||
|
||||
@ -75,6 +81,7 @@ pub async fn alias(
|
||||
name,
|
||||
args: list,
|
||||
block,
|
||||
infer,
|
||||
save,
|
||||
},
|
||||
_ctx,
|
||||
@ -82,17 +89,21 @@ pub async fn alias(
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
.replace("--save", "") // TODO using regex (or reconstruct string from AST?)
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input alias
|
||||
@ -133,9 +144,197 @@ pub async fn alias(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
if let Some(true) = infer {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
||||
block,
|
||||
),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
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 mut arg_shapes = HashMap::new();
|
||||
for pipeline in &block.block {
|
||||
for classified in &pipeline.list {
|
||||
match classified {
|
||||
ClassifiedCommand::Expr(spanned_expr) => {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
check_merge(&mut arg_shapes, &found)?
|
||||
}
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
if let Some(signature) = registry.get(&internal.name) {
|
||||
if let Some(positional) = &internal.args.positional {
|
||||
for (i, spanned_expr) in positional.iter().enumerate() {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
if i >= signature.positional.len() {
|
||||
if let Some((sig_shape, _)) = &signature.rest_positional {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
} else {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
} else {
|
||||
let (pos_type, _) = &signature.positional[i];
|
||||
match pos_type {
|
||||
// TODO pass on mandatory/optional?
|
||||
PositionalType::Mandatory(_, sig_shape)
|
||||
| PositionalType::Optional(_, sig_shape) => {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, val) in named.iter() {
|
||||
if let NamedValue::Value(_, spanned_expr) = val {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
match signature.named.get(name) {
|
||||
None => {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
Some((named_type, _)) => {
|
||||
if let NamedType::Mandatory(_, sig_shape)
|
||||
| NamedType::Optional(_, sig_shape) = named_type
|
||||
{
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!("registry has lost name it provided");
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arg_shapes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
@ -76,60 +77,56 @@ impl WholeStreamCommand for Ansi {
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
pub fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => {
|
||||
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
|
||||
}
|
||||
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => {
|
||||
Some(ansi_term::Color::Purple.underline().prefix().to_string())
|
||||
}
|
||||
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
|
||||
"g" | "green" => Some(Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
|
||||
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
|
||||
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -55,7 +55,7 @@ impl WholeStreamCommand for Autoenv {
|
||||
The file can contain several optional sections:
|
||||
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
|
||||
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
|
||||
scripts: scripts to run when entering the directory or leaving it. Note that exitscripts are not run in the directory they are declared in."#
|
||||
scripts: scripts to run when entering the directory or leaving it."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewConfiguration};
|
||||
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use crate::primitive::get_color_config;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_table::TextStyle;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
pub struct Autoview;
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoview {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"autoview"
|
||||
}
|
||||
@ -82,31 +84,17 @@ impl RunnableContextWithoutInput {
|
||||
}
|
||||
|
||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let configuration = AutoViewConfiguration::new();
|
||||
|
||||
let binary = context.get_command("binaryview");
|
||||
let text = context.get_command("textview");
|
||||
let table = context.get_command("table");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
let pivot_mode = crate::data::config::config(Tag::unknown());
|
||||
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
|
||||
match v.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Always,
|
||||
}
|
||||
} else {
|
||||
AutoPivotMode::Always
|
||||
};
|
||||
let pivot_mode = configuration.pivot_mode();
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
let color_hm = get_color_config();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
@ -254,8 +242,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
Value {
|
||||
value: UntaggedValue::Row(row),
|
||||
..
|
||||
} if pivot_mode == AutoPivotMode::Always
|
||||
|| (pivot_mode == AutoPivotMode::Auto
|
||||
} if pivot_mode.is_always()
|
||||
|| (pivot_mode.is_auto()
|
||||
&& (row
|
||||
.entries
|
||||
.iter()
|
||||
@ -271,15 +259,14 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
entries.push(vec![
|
||||
nu_table::StyledString::new(
|
||||
key.to_string(),
|
||||
nu_table::TextStyle {
|
||||
alignment: nu_table::Alignment::Left,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
TextStyle::new()
|
||||
.alignment(nu_table::Alignment::Left)
|
||||
.fg(ansi_term::Color::Green)
|
||||
.bold(Some(true)),
|
||||
),
|
||||
nu_table::StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
nu_table::TextStyle::basic(),
|
||||
nu_table::TextStyle::basic_left(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
@ -287,7 +274,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
let table =
|
||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||
|
||||
nu_table::draw_table(&table, term_width);
|
||||
nu_table::draw_table(&table, term_width, &color_hm);
|
||||
}
|
||||
|
||||
Value {
|
||||
@ -328,7 +315,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
positional: None,
|
||||
named: None,
|
||||
span,
|
||||
is_last: true,
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::new(),
|
||||
@ -338,12 +325,12 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Autoview;
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Autoview {})
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
4
crates/nu-cli/src/commands/autoview/mod.rs
Normal file
4
crates/nu-cli/src/commands/autoview/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod command;
|
||||
mod options;
|
||||
|
||||
pub use command::Command as Autoview;
|
60
crates/nu-cli/src/commands/autoview/options.rs
Normal file
60
crates/nu-cli/src/commands/autoview/options.rs
Normal file
@ -0,0 +1,60 @@
|
||||
pub use nu_data::config::NuConfig;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
impl AutoPivotMode {
|
||||
pub fn is_auto(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Auto => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_always(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Always => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn is_never(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Never => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConfigExtensions: Debug + Send {
|
||||
fn pivot_mode(&self) -> AutoPivotMode;
|
||||
}
|
||||
|
||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||
let vars = &config.vars;
|
||||
|
||||
if let Some(mode) = vars.get("pivot_mode") {
|
||||
let mode = match mode.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Never,
|
||||
};
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
AutoPivotMode::Never
|
||||
}
|
||||
|
||||
impl ConfigExtensions for NuConfig {
|
||||
fn pivot_mode(&self) -> AutoPivotMode {
|
||||
pivot_mode(self)
|
||||
}
|
||||
}
|
@ -1,18 +1,22 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
use heim::cpu::time;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
|
||||
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use chrono::prelude::*;
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct Benchmark;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BenchmarkArgs {
|
||||
block: Block,
|
||||
passthrough: Option<Block>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -22,15 +26,22 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("benchmark").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and benchmark",
|
||||
)
|
||||
Signature::build("benchmark")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and benchmark",
|
||||
)
|
||||
.named(
|
||||
"passthrough",
|
||||
SyntaxShape::Block,
|
||||
"Display the benchmark results and pass through the block's output",
|
||||
Some('p'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
|
||||
"Runs a block and returns the time it took to execute it"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -40,6 +51,21 @@ impl WholeStreamCommand for Benchmark {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Benchmarks a command within a block",
|
||||
example: "benchmark { sleep 500ms }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Benchmarks a command within a block and passes its output through",
|
||||
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
|
||||
result: Some(vec![UntaggedValue::int(45).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(
|
||||
@ -48,11 +74,15 @@ async fn benchmark(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let tag = raw_args.call_info.args.span;
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block }, input) = raw_args.process(®istry).await?;
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
let start_time: chrono::DateTime<_> = Utc::now();
|
||||
let start_time = Instant::now();
|
||||
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let start = time().await;
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
@ -63,18 +93,110 @@ async fn benchmark(
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let output = result?.into_vec().await;
|
||||
|
||||
let output = match result {
|
||||
Ok(mut stream) => {
|
||||
let _ = stream.drain_vec().await;
|
||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
||||
context.clear_errors();
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
||||
tag: Tag::from(block.span),
|
||||
}))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
Ok(OutputStream::from(vec![output]))
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let end = time().await;
|
||||
|
||||
let end_time = Instant::now();
|
||||
context.clear_errors();
|
||||
|
||||
// return basic runtime
|
||||
#[cfg(not(feature = "rich-benchmark"))]
|
||||
{
|
||||
let mut indexmap = IndexMap::with_capacity(1);
|
||||
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
}
|
||||
// return advanced stats
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
if let (Ok(start), Ok(end)) = (start, end) {
|
||||
let mut indexmap = IndexMap::with_capacity(4);
|
||||
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
|
||||
let user_time = into_big_int(end.user() - start.user());
|
||||
indexmap.insert("user time".to_string(), user_time);
|
||||
|
||||
let system_time = into_big_int(end.system() - start.system());
|
||||
indexmap.insert("system time".to_string(), system_time);
|
||||
|
||||
let idle_time = into_big_int(end.idle() - start.idle());
|
||||
indexmap.insert("idle time".to_string(), idle_time);
|
||||
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
} else {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not retreive CPU time",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark_output<T, Output>(
|
||||
indexmap: IndexMap<String, BigInt>,
|
||||
block_output: Output,
|
||||
passthrough: Option<Block>,
|
||||
tag: T,
|
||||
context: &mut EvaluationContext,
|
||||
scope: &Scope,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
T: Into<Tag> + Copy,
|
||||
Output: Into<OutputStream>,
|
||||
{
|
||||
let value = UntaggedValue::Row(Dictionary::from(
|
||||
indexmap
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
|
||||
.collect::<IndexMap<String, Value>>(),
|
||||
))
|
||||
.into_value(tag);
|
||||
|
||||
if let Some(time_block) = passthrough {
|
||||
let benchmark_output = InputStream::one(value);
|
||||
|
||||
// add autoview for an empty block
|
||||
let time_block = add_implicit_autoview(time_block);
|
||||
|
||||
let _ = run_block(
|
||||
&time_block,
|
||||
context,
|
||||
benchmark_output,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?;
|
||||
context.clear_errors();
|
||||
|
||||
Ok(block_output.into())
|
||||
} else {
|
||||
let benchmark_output = OutputStream::one(value);
|
||||
Ok(benchmark_output)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_implicit_autoview(mut block: Block) -> Block {
|
||||
if block.block.is_empty() {
|
||||
block.push({
|
||||
let mut commands = Commands::new(block.span);
|
||||
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
|
||||
"autoview".to_string(),
|
||||
block.span,
|
||||
block.span,
|
||||
)));
|
||||
commands
|
||||
});
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
|
||||
time.try_into()
|
||||
.unwrap_or_else(|_| Duration::new(0, 0))
|
||||
.as_nanos()
|
||||
.into()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -100,7 +100,7 @@ pub async fn cal(
|
||||
(current_month, current_month)
|
||||
};
|
||||
|
||||
let add_months_of_year_to_table_result = add_months_of_year_to_table(
|
||||
add_months_of_year_to_table(
|
||||
&args,
|
||||
&mut calendar_vec_deque,
|
||||
&tag,
|
||||
@ -108,12 +108,9 @@ pub async fn cal(
|
||||
month_range,
|
||||
current_month,
|
||||
current_day_option,
|
||||
);
|
||||
)?;
|
||||
|
||||
match add_months_of_year_to_table_result {
|
||||
Ok(()) => Ok(futures::stream::iter(calendar_vec_deque).to_output_stream()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
|
||||
}
|
||||
|
||||
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
||||
|
@ -94,6 +94,35 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||
|
||||
// Weather symbols
|
||||
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
|
||||
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
|
||||
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
|
||||
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
|
||||
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
|
||||
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
|
||||
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
|
||||
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
|
||||
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
|
||||
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
// Ansi Erase Sequences
|
||||
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
|
||||
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
|
||||
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
|
||||
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
|
||||
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
@ -11,7 +11,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
ctx: &mut EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
@ -64,33 +64,27 @@ pub(crate) async fn run_block(
|
||||
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
ctx: &mut EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut iter = commands.list.clone().into_iter().peekable();
|
||||
loop {
|
||||
let item: Option<ClassifiedCommand> = iter.next();
|
||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
||||
|
||||
input = match (item, next) {
|
||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
||||
for item in commands.list.clone() {
|
||||
input = match item {
|
||||
ClassifiedCommand::Dynamic(_) => {
|
||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
||||
ClassifiedCommand::Expr(expr) => {
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
}
|
||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
||||
|
||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
||||
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||
|
||||
ClassifiedCommand::Internal(left) => {
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
}
|
||||
|
||||
(None, _) => break,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use nu_protocol::Value;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
|
@ -13,16 +13,16 @@ use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ExternalCommand;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
@ -34,15 +34,15 @@ pub(crate) async fn run_external_command(
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, scope, is_last).await
|
||||
run_with_stdin(command, context, input, scope, external_redirection).await
|
||||
}
|
||||
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
@ -62,9 +62,29 @@ async fn run_with_stdin(
|
||||
}
|
||||
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
|
||||
command_args.push(trimmed_value_string);
|
||||
match &value.value {
|
||||
UntaggedValue::Table(table) => {
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args
|
||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not convert to positional arguments",
|
||||
"could not convert to positional arguments",
|
||||
value.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
command_args.push(trimmed_value_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
@ -102,7 +122,14 @@ async fn run_with_stdin(
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(&command, &path, &process_args[..], input, is_last, scope)
|
||||
spawn(
|
||||
&command,
|
||||
&path,
|
||||
&process_args[..],
|
||||
input,
|
||||
external_redirection,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
@ -110,7 +137,7 @@ fn spawn(
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
is_last: bool,
|
||||
external_redirection: ExternalRedirection,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
@ -146,12 +173,22 @@ fn spawn(
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
if !is_last {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
match external_redirection {
|
||||
ExternalRedirection::Stdout => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
}
|
||||
ExternalRedirection::Stderr => {
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
ExternalRedirection::StdoutAndStderr => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
@ -235,7 +272,9 @@ fn spawn(
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !is_last {
|
||||
if external_redirection == ExternalRedirection::Stdout
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||
stdout
|
||||
} else {
|
||||
@ -250,20 +289,6 @@ fn spawn(
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stdout);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
|
||||
@ -317,17 +342,34 @@ fn spawn(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if external_redirection == ExternalRedirection::Stderr
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stderr);
|
||||
let err_stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
|
||||
for err_line in block_on_stream(err_stream) {
|
||||
match err_line {
|
||||
for line in block_on_stream(stream) {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s.clone()),
|
||||
ShellError::untagged_runtime_error(s),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
@ -339,9 +381,7 @@ fn spawn(
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
"Binary in stderr output",
|
||||
),
|
||||
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
@ -363,8 +403,8 @@ fn spawn(
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stderr ({})", e),
|
||||
"unable to read from stderr",
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
@ -385,7 +425,7 @@ fn spawn(
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
let cfg = nu_data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
@ -435,10 +475,11 @@ pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
|
||||
if which::which(name).is_ok() {
|
||||
true
|
||||
} else {
|
||||
// Reference: https://ss64.com/nt/syntax-internal.html
|
||||
let cmd_builtins = [
|
||||
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
|
||||
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
|
||||
"mklink",
|
||||
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
|
||||
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
|
||||
"rmdir", "set", "start", "time", "title", "type", "ver", "verify", "vol",
|
||||
];
|
||||
|
||||
cmd_builtins.contains(&name)
|
||||
@ -502,7 +543,7 @@ mod tests {
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, Context, InputStream};
|
||||
use super::{run_external_command, EvaluationContext, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use futures::executor::block_on;
|
||||
@ -528,16 +569,22 @@ mod tests {
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
async fn non_existent_run() -> Result<(), ShellError> {
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
let mut ctx =
|
||||
EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(run_external_command(
|
||||
cmd,
|
||||
&mut ctx,
|
||||
input,
|
||||
&Scope::new(),
|
||||
ExternalRedirection::Stdout
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -545,7 +592,7 @@ mod tests {
|
||||
// async fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
// let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// .await?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
@ -4,12 +4,12 @@ use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
use log::{log_enabled, trace};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::InternalCommand;
|
||||
use nu_protocol::hir::{ExternalRedirection, InternalCommand};
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
@ -87,7 +87,7 @@ pub(crate) async fn run_internal_command(
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
@ -182,10 +182,7 @@ pub(crate) async fn run_internal_command(
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match FilesystemShell::with_location(
|
||||
location,
|
||||
context.registry().clone(),
|
||||
) {
|
||||
match FilesystemShell::with_location(location) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
@ -203,6 +200,28 @@ pub(crate) async fn run_internal_command(
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddPlugins(path) => {
|
||||
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||
Ok(plugins) => {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| {
|
||||
!context.is_command_registered(p.name())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
Err(reason) => {
|
||||
context.error(reason.clone());
|
||||
InputStream::one(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
|
@ -4,6 +4,7 @@ pub(crate) mod expr;
|
||||
pub(crate) mod external;
|
||||
pub(crate) mod internal;
|
||||
pub(crate) mod maybe_text_codec;
|
||||
pub(crate) mod plugin;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use dynamic::Command as DynamicCommand;
|
||||
|
@ -1,30 +1,16 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::commands::command::{whole_stream_command, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::jsonrpc::JsonRpc;
|
||||
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JsonRpc<T> {
|
||||
jsonrpc: String,
|
||||
pub method: String,
|
||||
pub params: T,
|
||||
}
|
||||
|
||||
impl<T> JsonRpc<T> {
|
||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
||||
JsonRpc {
|
||||
jsonrpc: "2.0".into(),
|
||||
method: method.into(),
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "method")]
|
||||
@ -35,15 +21,77 @@ pub enum NuResult {
|
||||
},
|
||||
}
|
||||
|
||||
enum PluginCommand {
|
||||
Filter(PluginFilter),
|
||||
Sink(PluginSink),
|
||||
}
|
||||
|
||||
impl PluginCommand {
|
||||
fn command(self) -> Result<crate::commands::Command, ShellError> {
|
||||
match self {
|
||||
PluginCommand::Filter(cmd) => Ok(whole_stream_command(cmd)),
|
||||
PluginCommand::Sink(cmd) => Ok(whole_stream_command(cmd)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PluginMode {
|
||||
Filter,
|
||||
Sink,
|
||||
}
|
||||
|
||||
pub struct PluginCommandBuilder {
|
||||
mode: PluginMode,
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
impl PluginCommandBuilder {
|
||||
pub fn new(
|
||||
name: impl Into<String>,
|
||||
path: impl Into<String>,
|
||||
config: impl Into<Signature>,
|
||||
) -> Self {
|
||||
let config = config.into();
|
||||
|
||||
PluginCommandBuilder {
|
||||
mode: if config.is_filter {
|
||||
PluginMode::Filter
|
||||
} else {
|
||||
PluginMode::Sink
|
||||
},
|
||||
name: name.into(),
|
||||
path: path.into(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Result<crate::commands::Command, ShellError> {
|
||||
let mode = &self.mode;
|
||||
|
||||
let name = self.name.clone();
|
||||
let path = self.path.clone();
|
||||
let config = self.config.clone();
|
||||
|
||||
let cmd = match mode {
|
||||
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
|
||||
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
|
||||
};
|
||||
|
||||
cmd.command()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct PluginCommand {
|
||||
pub struct PluginFilter {
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PluginCommand {
|
||||
impl WholeStreamCommand for PluginFilter {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
@ -61,11 +109,11 @@ impl WholeStreamCommand for PluginCommand {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
filter_plugin(self.path.clone(), args, registry).await
|
||||
run_filter(self.path.clone(), args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn filter_plugin(
|
||||
async fn run_filter(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -84,11 +132,34 @@ pub async fn filter_plugin(
|
||||
|
||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
let ps1_file = match ext {
|
||||
Some(ext) => ext == "ps1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
let mut child: Child = if ps1_file {
|
||||
Command::new("pwsh")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.args(&[
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
&real_path.to_string_lossy(),
|
||||
])
|
||||
.spawn()
|
||||
.expect("Failed to spawn PowerShell process")
|
||||
} else {
|
||||
Command::new(path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process")
|
||||
};
|
||||
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
@ -111,6 +182,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("begin_filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
@ -136,6 +208,8 @@ pub async fn filter_plugin(
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("begin_filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -168,6 +242,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("end_filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
@ -193,6 +268,8 @@ pub async fn filter_plugin(
|
||||
let stream = match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("end_filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -220,6 +297,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("quit:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
@ -246,6 +324,8 @@ pub async fn filter_plugin(
|
||||
|
||||
let request = JsonRpc::new("filter", v);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
|
||||
@ -262,6 +342,8 @@ pub async fn filter_plugin(
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -313,11 +395,11 @@ impl WholeStreamCommand for PluginSink {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sink_plugin(self.path.clone(), args, registry).await
|
||||
run_sink(self.path.clone(), args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sink_plugin(
|
||||
async fn run_sink(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -335,7 +417,33 @@ pub async fn sink_plugin(
|
||||
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||
let _ = tmpfile.flush();
|
||||
|
||||
let child = std::process::Command::new(path).arg(tmpfile.path()).spawn();
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
let ps1_file = match ext {
|
||||
Some(ext) => ext == "ps1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
// TODO: This sink may not work in powershell, trying to find
|
||||
// an example of what CallInfo would look like in this temp file
|
||||
let child = if ps1_file {
|
||||
Command::new("pwsh")
|
||||
.args(&[
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
&real_path.to_string_lossy(),
|
||||
&tmpfile
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("Failed getting tmpfile path"),
|
||||
])
|
||||
.spawn()
|
||||
} else {
|
||||
Command::new(path).arg(&tmpfile.path()).spawn()
|
||||
};
|
||||
|
||||
if let Ok(mut child) = child {
|
||||
let _ = child.wait();
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clears the terminal"
|
||||
"Clears the terminal"
|
||||
}
|
||||
|
||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::help::get_help;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
@ -262,10 +262,10 @@ impl EvaluatedCommandArgs {
|
||||
|
||||
/// Get the nth positional argument, error if not possible
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||
match self.call_info.args.nth(pos) {
|
||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
||||
Some(item) => Ok(item),
|
||||
}
|
||||
self.call_info
|
||||
.args
|
||||
.nth(pos)
|
||||
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||
@ -303,6 +303,11 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
false
|
||||
}
|
||||
|
||||
// Commands that are not meant to be run by users
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
@ -367,6 +372,10 @@ impl Command {
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn is_internal(&self) -> bool {
|
||||
self.0.is_internal()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
@ -49,7 +49,7 @@ impl WholeStreamCommand for Compact {
|
||||
},
|
||||
Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -af | compact target",
|
||||
example: "ls -la | compact target",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
@ -45,7 +45,7 @@ pub async fn clear(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.clear();
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl WholeStreamCommand for Command {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -56,7 +56,7 @@ pub async fn get(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
@ -50,7 +50,7 @@ pub async fn set(
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
let result = crate::data::config::read(name_span, &Some(configuration))?;
|
||||
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
@ -41,7 +41,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Remove the startup commands",
|
||||
example: "config --remove startup",
|
||||
example: "config remove startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
@ -54,7 +54,7 @@ pub async fn remove(
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -39,8 +39,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set completion_mode to circular",
|
||||
example: "config set [completion_mode circular]",
|
||||
description: "Set nonzero_exit_errors to true",
|
||||
example: "config set nonzero_exit_errors $true",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
@ -55,7 +55,7 @@ pub async fn set(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -58,9 +58,9 @@ pub async fn set_into(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
// In the original code, this is set to `Some` if the `--load flag is set`
|
||||
// In the original code, this is set to `Some` if the `load flag is set`
|
||||
let configuration = None;
|
||||
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
@ -7,6 +7,11 @@ use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CountArgs {
|
||||
column: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,7 +19,11 @@ impl WholeStreamCommand for Count {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("count")
|
||||
Signature::build("count").switch(
|
||||
"column",
|
||||
"Calculate number of columns in table",
|
||||
Some('c'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -24,22 +33,47 @@ impl WholeStreamCommand for Count {
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let rows: Vec<Value> = args.input.collect().await;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (CountArgs { column }, input) = args.process(®istry).await?;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::int(rows.len()).into_value(name),
|
||||
))
|
||||
let count = if column {
|
||||
if rows.is_empty() {
|
||||
0
|
||||
} else {
|
||||
match &rows[0].value {
|
||||
UntaggedValue::Row(dictionary) => dictionary.length(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Cannot obtain column count",
|
||||
"cannot obtain column count",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rows.len()
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(UntaggedValue::int(count).into_value(tag)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Count the number of columns in the calendar table",
|
||||
example: "cal | count -c",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -1,175 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date")
|
||||
.switch("utc", "use universal time (UTC)", Some('u'))
|
||||
.switch("local", "use the local time", Some('l'))
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"report datetime in supplied strftime format",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the current datetime."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current local time and date",
|
||||
example: "date",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current UTC time and date",
|
||||
example: "date --utc",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it based on format",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it without a table",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
||||
|
||||
pub async fn date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let raw = args.has("raw");
|
||||
|
||||
let dt_fmt = if args.has("format") {
|
||||
if let Some(dt_fmt) = args.get("format") {
|
||||
dt_fmt.convert_to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let value = if args.has("utc") {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(utc, tag, dt_fmt)
|
||||
}
|
||||
} else {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Date {})
|
||||
}
|
||||
}
|
46
crates/nu-cli/src/commands/date/command.rs
Normal file
46
crates/nu-cli/src/commands/date/command.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Apply date function"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
63
crates/nu-cli/src/commands/date/format.rs
Normal file
63
crates/nu-cli/src/commands/date/format.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
format: Tagged<String>,
|
||||
raw: Option<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date format")
|
||||
.required("format", SyntaxShape::String, "strftime format")
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"format the current date using the given format string."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
format(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn format(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (FormatArgs { format, raw }, _) = args.process(®istry).await?;
|
||||
|
||||
let dt_fmt = format.to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if let Some(true) = raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
11
crates/nu-cli/src/commands/date/mod.rs
Normal file
11
crates/nu-cli/src/commands/date/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
pub mod command;
|
||||
pub mod format;
|
||||
pub mod now;
|
||||
pub mod utc;
|
||||
|
||||
mod utils;
|
||||
|
||||
pub use command::Command as Date;
|
||||
pub use format::Date as DateFormat;
|
||||
pub use now::Date as DateNow;
|
||||
pub use utc::Date as DateUTC;
|
50
crates/nu-cli/src/commands/date/now.rs
Normal file
50
crates/nu-cli/src/commands/date/now.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date now"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date now")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
now(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn now(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
50
crates/nu-cli/src/commands/date/utc.rs
Normal file
50
crates/nu-cli/src/commands/date/utc.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date utc"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date utc")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
utc(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Utc> = Utc::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
64
crates/nu-cli/src/commands/date/utils.rs
Normal file
64
crates/nu-cli/src/commands/date/utils.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::DateTime;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::UntaggedValue;
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -45,7 +45,7 @@ impl WholeStreamCommand for Default {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Give a default 'target' to all file entries",
|
||||
example: "ls -af | default target 'nothing'",
|
||||
example: "ls -la | default target 'nothing'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
@ -60,13 +60,13 @@ async fn default(
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let should_add = match item {
|
||||
let should_add = matches!(
|
||||
item,
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => r.get_data(&column.item).borrow().is_none(),
|
||||
_ => false,
|
||||
};
|
||||
} if r.get_data(&column.item).borrow().is_none()
|
||||
);
|
||||
|
||||
if should_add {
|
||||
match item.insert_data_at_path(&column.item, value.clone()) {
|
||||
|
@ -2,7 +2,9 @@ use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
@ -61,9 +63,9 @@ async fn do_(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let is_last = raw_args.call_info.args.is_last;
|
||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
@ -72,7 +74,26 @@ async fn do_(
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
block.set_is_last(is_last);
|
||||
|
||||
let block_redirection = match external_redirection {
|
||||
ExternalRedirection::None => {
|
||||
if ignore_errors {
|
||||
ExternalRedirection::Stderr
|
||||
} else {
|
||||
ExternalRedirection::None
|
||||
}
|
||||
}
|
||||
ExternalRedirection::Stdout => {
|
||||
if ignore_errors {
|
||||
ExternalRedirection::StdoutAndStderr
|
||||
} else {
|
||||
ExternalRedirection::Stdout
|
||||
}
|
||||
}
|
||||
x => x,
|
||||
};
|
||||
|
||||
block.set_redirect(block_redirection);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
@ -85,6 +106,9 @@ async fn do_(
|
||||
.await;
|
||||
|
||||
if ignore_errors {
|
||||
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
||||
// any errors we see in the process.
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec().await;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
@ -22,12 +22,12 @@ impl WholeStreamCommand for Drop {
|
||||
Signature::build("drop").optional(
|
||||
"rows",
|
||||
SyntaxShape::Number,
|
||||
"starting from the back, the number of rows to drop",
|
||||
"starting from the back, the number of rows to remove",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Drop the last number of rows."
|
||||
"Remove the last number of rows. If you want to remove columns, try 'reject'."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::stream::once;
|
||||
@ -84,7 +84,7 @@ pub async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<Context>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input_clone = input.clone();
|
||||
@ -105,6 +105,14 @@ pub async fn process_row(
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
let mut dict = TaggedDictBuilder::new(item.tag());
|
||||
dict.insert_untagged("index", UntaggedValue::int(index));
|
||||
dict.insert_value("item", item);
|
||||
|
||||
dict.into_value()
|
||||
}
|
||||
|
||||
async fn each(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -112,7 +120,7 @@ async fn each(
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
|
||||
@ -124,13 +132,10 @@ async fn each(
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(input.1.tag());
|
||||
dict.insert_untagged("index", UntaggedValue::int(input.0));
|
||||
dict.insert_value("item", input.1);
|
||||
let row = make_indexed_item(input.0, input.1);
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, dict.into_value()).await {
|
||||
match process_row(block, scope, head, context, row).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
133
crates/nu-cli/src/commands/each/group.rs
Normal file
133
crates/nu-cli/src/commands/each/group.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use crate::commands::each::process_row;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct EachGroup;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachGroupArgs {
|
||||
group_size: Tagged<usize>,
|
||||
block: Block,
|
||||
//numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachGroup {
|
||||
fn name(&self) -> &str {
|
||||
"each group"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each group")
|
||||
.required("group_size", SyntaxShape::Int, "the size of each group")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each group",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block on groups of `group_size` rows of a table at a time."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Echo the sum of each pair",
|
||||
example: "echo [1 2 3 4] | each group 2 { echo $it | math sum }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®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(),
|
||||
head.clone(),
|
||||
context.clone(),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
let value = Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag: Tag::unknown(),
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, value).await {
|
||||
Ok(s) => {
|
||||
// We need to handle this differently depending on whether process_row
|
||||
// returned just 1 value or if it returned multiple as a stream.
|
||||
let vec = s.collect::<Vec<_>>().await;
|
||||
|
||||
// If it returned just one value, just take that value
|
||||
if vec.len() == 1 {
|
||||
return OutputStream::one(vec.into_iter().next().expect(
|
||||
"This should be impossible, we just checked that vec.len() == 1.",
|
||||
));
|
||||
}
|
||||
|
||||
// If it returned multiple values, we need to put them into a table and
|
||||
// return that.
|
||||
let result = vec.into_iter().collect::<Result<Vec<ReturnSuccess>, _>>();
|
||||
let result_table = match result {
|
||||
Ok(t) => t,
|
||||
Err(e) => return OutputStream::one(Err(e)),
|
||||
};
|
||||
|
||||
let table = result_table
|
||||
.into_iter()
|
||||
.filter_map(|x| x.raw_value())
|
||||
.collect();
|
||||
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(UntaggedValue::Table(table).into())))
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachGroup;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachGroup {})
|
||||
}
|
||||
}
|
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;
|
113
crates/nu-cli/src/commands/each/window.rs
Normal file
113
crates/nu-cli/src/commands/each/window.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::commands::each::group::run_block_on_vec;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
//use itertools::Itertools;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Primitive, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct EachWindow;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachWindowArgs {
|
||||
window_size: Tagged<usize>,
|
||||
block: Block,
|
||||
stride: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachWindow {
|
||||
fn name(&self) -> &str {
|
||||
"each window"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each window")
|
||||
.required("window_size", SyntaxShape::Int, "the size of each window")
|
||||
.named(
|
||||
"stride",
|
||||
SyntaxShape::Int,
|
||||
"the number of rows to slide over between windows",
|
||||
Some('s'),
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each group",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block on sliding windows of `window_size` rows of a table at a time."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Echo the sum of each window",
|
||||
example: "echo [1 2 3 4] | each window 2 { echo $it | math sum }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®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 head = head.clone();
|
||||
let context = context.clone();
|
||||
let local_window = window.clone();
|
||||
|
||||
async move {
|
||||
if i % stride == 0 {
|
||||
Some(run_block_on_vec(local_window, block, scope, head, context).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|x| async { x })
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachWindow;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachWindow {})
|
||||
}
|
||||
}
|
@ -81,17 +81,20 @@ struct RangeIterator {
|
||||
end: Primitive,
|
||||
tag: Tag,
|
||||
is_end_inclusive: bool,
|
||||
is_done: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||
let start = match range.from.0.item {
|
||||
Primitive::Nothing => Primitive::Int(0.into()),
|
||||
x => x,
|
||||
};
|
||||
|
||||
RangeIterator {
|
||||
curr: range.from.0.item,
|
||||
curr: start,
|
||||
end: range.to.0.item,
|
||||
tag,
|
||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||
is_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,14 +102,40 @@ impl RangeIterator {
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Result<ReturnSuccess, ShellError>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr != self.end {
|
||||
let ordering = if self.end == Primitive::Nothing {
|
||||
Ordering::Less
|
||||
} else {
|
||||
let result =
|
||||
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot create range",
|
||||
"unsupported range",
|
||||
self.tag.span,
|
||||
)
|
||||
});
|
||||
|
||||
if let Err(result) = result {
|
||||
return Some(Err(result));
|
||||
}
|
||||
|
||||
let result = result
|
||||
.expect("Internal error: the error case was already protected, but that failed");
|
||||
|
||||
result.compare()
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
|
||||
self.curr = match crate::data::value::compute_values(
|
||||
let next_value = nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
) {
|
||||
);
|
||||
|
||||
self.curr = match next_value {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
} else if self.is_end_inclusive && !self.is_done {
|
||||
self.is_done = true;
|
||||
Some(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
|
||||
))
|
||||
} else {
|
||||
// TODO: add inclusive/exclusive ranges
|
||||
None
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
use nu_protocol::{
|
||||
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
@ -145,7 +146,7 @@ async fn enter(
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
scope: scope.clone(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
87
crates/nu-cli/src/commands/exec.rs
Normal file
87
crates/nu-cli/src/commands/exec.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Exec;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ExecArgs {
|
||||
pub command: Tagged<PathBuf>,
|
||||
pub rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exec {
|
||||
fn name(&self) -> &str {
|
||||
"exec"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("exec")
|
||||
.required("command", SyntaxShape::Path, "the command to execute")
|
||||
.rest(SyntaxShape::Pattern, "any additional arguments for command")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Execute command"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exec(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Execute 'ps aux'",
|
||||
example: "exec ps aux",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Execute 'nautilus'",
|
||||
example: "exec nautilus",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _): (ExecArgs, _) = args.process(®istry).await?;
|
||||
|
||||
let mut command = Command::new(args.command.item);
|
||||
for tagged_arg in args.rest {
|
||||
command.arg(tagged_arg.item);
|
||||
}
|
||||
|
||||
let err = command.exec(); // this replaces our process, should not return
|
||||
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
format!("{}", err),
|
||||
&name,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
"exec is not supported on your platform",
|
||||
&args.call_info.name_tag,
|
||||
))
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::command::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
|
||||
.delimiter(separator as u8)
|
||||
.from_reader(s.as_bytes());
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
let headers = if headerless {
|
||||
(1..=reader.headers()?.len())
|
||||
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
|
||||
if let Ok(i) = value.parse::<i64>() {
|
||||
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
||||
} else if let Ok(f) = value.parse::<f64>() {
|
||||
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
|
||||
tagged_row.insert_value(
|
||||
header,
|
||||
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
|
||||
)
|
||||
} else {
|
||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
||||
}
|
||||
|
@ -39,11 +39,12 @@ impl WholeStreamCommand for FromJSON {
|
||||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::String(s) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
@ -45,6 +45,7 @@ async fn from_ods(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
|
||||
let (
|
||||
@ -73,7 +74,7 @@ async fn from_ods(
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
|
@ -30,11 +30,12 @@ impl WholeStreamCommand for FromTOML {
|
||||
|
||||
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
|
||||
toml::Value::Float(n) => UntaggedValue::decimal(*n).into_value(tag),
|
||||
toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
|
||||
toml::Value::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
@ -45,6 +45,7 @@ async fn from_xlsx(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromXLSXArgs {
|
||||
@ -73,7 +74,7 @@ async fn from_xlsx(
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
|
@ -135,7 +135,6 @@ async fn from_xml(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::commands::from_xml;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
|
@ -58,6 +58,7 @@ fn convert_yaml_value_to_nu_value(
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
let err_not_compatible_number = ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||
span,
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
let result: Result<Vec<Value>, ShellError> = a
|
||||
|
@ -4,10 +4,10 @@ use indexmap::set::IndexSet;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
|
||||
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_source::HasFallibleSpan;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
pub struct Get;
|
||||
@ -58,195 +58,209 @@ impl WholeStreamCommand for Get {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
let fields = path.clone();
|
||||
|
||||
get_data_by_column_path(
|
||||
obj,
|
||||
path,
|
||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
||||
let path_members_span = span_for_spanned_list(fields.members().iter().map(|p| p.span));
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
for row in rows {
|
||||
for field in row.data_descriptors() {
|
||||
if !existing_columns.contains(&field[..]) {
|
||||
existing_columns.insert(field.clone());
|
||||
names.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
"Appears to contain rows. Try indexing instead.",
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
} else {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions
|
||||
.iter()
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
};
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
let total = rows.len();
|
||||
|
||||
let secondary_label = if total == 1 {
|
||||
"The table only has 1 row".to_owned()
|
||||
} else {
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
format!("There isn't a row indexed at {}", idx),
|
||||
column_path_tried.span,
|
||||
secondary_label,
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
},
|
||||
UntaggedValue::Row(columns) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
&obj_source.data_descriptors().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"No rows available",
|
||||
format!("A row at '{}' can't be indexed.", &idx),
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Appears to contain columns. Columns available: {}",
|
||||
columns.keys().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
|
||||
error
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let (GetArgs { rest: column_paths }, mut input) = args.process(®istry).await?;
|
||||
if column_paths.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
Ok(input
|
||||
trace!("get {:?}", column_paths);
|
||||
let output_stream = input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
let output = column_paths
|
||||
.iter()
|
||||
.map(move |path| get_output(&item, path))
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
.collect::<Vec<_>>();
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
.to_output_stream();
|
||||
Ok(output_stream)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
|
||||
match get_column_path(path, item) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
}) => vec![],
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
|
||||
Ok(other) => vec![ReturnSuccess::value(other)],
|
||||
Err(reason) => vec![ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
|
||||
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => {
|
||||
return get_column_path_from_table_error(
|
||||
rows,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
);
|
||||
}
|
||||
UntaggedValue::Row(columns) => {
|
||||
if let Some(error) = get_column_from_row_error(
|
||||
columns,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
obj_source,
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
} else {
|
||||
error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_column_path_from_table_error(
|
||||
rows: &[Value],
|
||||
column_path_tried: &PathMember,
|
||||
path_members_span: &Span,
|
||||
) -> ShellError {
|
||||
match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
for row in rows {
|
||||
for field in row.data_descriptors() {
|
||||
if !existing_columns.contains(&field[..]) {
|
||||
existing_columns.insert(field.clone());
|
||||
names.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
"Appears to contain rows. Try indexing instead.",
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions
|
||||
.iter()
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
let total = rows.len();
|
||||
|
||||
let secondary_label = if total == 1 {
|
||||
"The table only has 1 row".to_owned()
|
||||
} else {
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
format!("There isn't a row indexed at {}", idx),
|
||||
column_path_tried.span,
|
||||
secondary_label,
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_from_row_error(
|
||||
columns: &Dictionary,
|
||||
column_path_tried: &PathMember,
|
||||
path_members_span: &Span,
|
||||
obj_source: &Value,
|
||||
) -> Option<ShellError> {
|
||||
match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
Some(ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
&obj_source.data_descriptors().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => Some(ShellError::labeled_error_with_secondary(
|
||||
"No rows available",
|
||||
format!("A row at '{}' can't be indexed.", &idx),
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Appears to contain columns. Columns available: {}",
|
||||
columns.keys().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ impl WholeStreamCommand for GroupBy {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"create a new table grouped."
|
||||
"Create a new table grouped."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -96,7 +96,7 @@ pub async fn group_by(
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(args.call_info.args.head.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
@ -169,7 +169,7 @@ pub async fn group_by(
|
||||
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
match group_strategy {
|
||||
let group_value = match group_strategy {
|
||||
Grouper::ByBlock => {
|
||||
let map = keys.clone();
|
||||
|
||||
@ -179,16 +179,12 @@ pub async fn group_by(
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(column_name) => match group(&column_name, &values, name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(reason) => Err(reason),
|
||||
},
|
||||
}
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(group_value?)))
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
@ -238,15 +234,12 @@ pub fn group(
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| match as_string(row) {
|
||||
Ok(group_key) => Ok(group_key),
|
||||
Err(reason) => Err(reason),
|
||||
});
|
||||
let block = Box::new(move |_, row: &Value| as_string(row));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByBlock => Err(ShellError::unimplemented(
|
||||
"Block not implemented: This should never happen.",
|
||||
@ -257,77 +250,32 @@ pub fn group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::group;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::*;
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
UntaggedValue::row(entries).into_untagged_value()
|
||||
}
|
||||
|
||||
fn table(list: &[Value]) -> Value {
|
||||
UntaggedValue::table(list).into_untagged_value()
|
||||
}
|
||||
|
||||
fn nu_releases_committers() -> Vec<Value> {
|
||||
vec![
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||
let for_key = Some(String::from("date").tagged_unknown());
|
||||
let sample = table(&nu_releases_committers());
|
||||
let sample = table(&committers());
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, &sample, Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
|
||||
"2019-07-23".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-07-23"), "chickens".into() => int(10) }),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-07-23"), "chickens".into() => int(5) }),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-07-23"), "chickens".into() => int(2) })
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
"2019-10-10".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-10-10"), "chickens".into() => int(6) }),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-10-10"), "chickens".into() => int(15) }),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-10-10"), "chickens".into() => int(30) })
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
|
||||
"2019-09-24".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-09-24"), "chickens".into() => int(20) }),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-09-24"), "chickens".into() => int(4) }),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-09-24"), "chickens".into() => int(10) })
|
||||
]),
|
||||
})
|
||||
);
|
||||
@ -338,25 +286,25 @@ mod tests {
|
||||
#[test]
|
||||
fn groups_table_by_country_column() -> Result<(), ShellError> {
|
||||
let for_key = Some(String::from("country").tagged_unknown());
|
||||
let sample = table(&nu_releases_committers());
|
||||
let sample = table(&committers());
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, &sample, Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"EC".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-07-23"), "chickens".into() => int(10) }),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-09-24"), "chickens".into() => int(20) }),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => date("2019-10-10"), "chickens".into() => int(30) })
|
||||
]),
|
||||
"NZ".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-07-23"), "chickens".into() => int(5) }),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-10-10"), "chickens".into() => int(15) }),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => date("2019-09-24"), "chickens".into() => int(10) })
|
||||
]),
|
||||
"US".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-10-10"), "chickens".into() => int(6) }),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-09-24"), "chickens".into() => int(4) }),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => date("2019-07-23"), "chickens".into() => int(2) }),
|
||||
]),
|
||||
})
|
||||
);
|
||||
|
@ -100,38 +100,36 @@ pub async fn group_by_date(
|
||||
|
||||
let value_result = match (grouper_date, grouper_column) {
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format("%Y-%b-%d"));
|
||||
let block = Box::new(move |_, row: &Value| row.format("%Y-%m-%d"));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
|
||||
Some(group_key) => Ok(group_key),
|
||||
None => Err(suggestions(column_name.borrow_tagged(), &row)),
|
||||
};
|
||||
let group_key = row
|
||||
.get_data_by_key(column_name.borrow_spanned())
|
||||
.ok_or_else(|| suggestions(column_name.borrow_tagged(), &row));
|
||||
|
||||
group_key?.format("%Y-%b-%d")
|
||||
group_key?.format("%Y-%m-%d")
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format(&fmt));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
|
||||
Some(group_key) => Ok(group_key),
|
||||
None => Err(suggestions(column_name.borrow_tagged(), &row)),
|
||||
};
|
||||
let group_key = row
|
||||
.get_data_by_key(column_name.borrow_spanned())
|
||||
.ok_or_else(|| suggestions(column_name.borrow_tagged(), &row));
|
||||
|
||||
group_key?.format(&fmt)
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Dictionary;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Headers;
|
||||
|
||||
@ -32,11 +32,18 @@ impl WholeStreamCommand for Headers {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Don't panic on rows with different headers",
|
||||
example: r#"echo "a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,8 +91,13 @@ pub async fn headers(
|
||||
match &r.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let mut entries = IndexMap::new();
|
||||
for (i, (_, v)) in d.entries.iter().enumerate() {
|
||||
entries.insert(headers[i].clone(), v.clone());
|
||||
for (i, header) in headers.iter().enumerate() {
|
||||
let value = match d.entries.get_index(i) {
|
||||
Some((_, value)) => value.clone(),
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing).into(),
|
||||
};
|
||||
|
||||
entries.insert(header.clone(), value);
|
||||
}
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::commands::command::Command;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_data::command::signature_dict;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
@ -38,6 +38,21 @@ impl WholeStreamCommand for Help {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut cmd_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
|
||||
|
||||
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
|
||||
|
||||
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
|
||||
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
|
||||
|
||||
cmd_dict.into_value()
|
||||
}
|
||||
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
@ -48,54 +63,114 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
Ok(
|
||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
return None;
|
||||
}
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
let (mut subcommand_names, command_names) = sorted_names
|
||||
.into_iter()
|
||||
// Internal only commands shouldn't be displayed
|
||||
.filter(|cmd_name| {
|
||||
registry
|
||||
.get_command(&cmd_name)
|
||||
.filter(|command| !command.is_internal())
|
||||
.is_some()
|
||||
})
|
||||
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
|
||||
|
||||
fn process_name(
|
||||
dict: &mut TaggedDictBuilder,
|
||||
cmd_name: &str,
|
||||
registry: CommandRegistry,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<(), ShellError> {
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
registry.get_command(&cmd_name).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd_name),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
})?,
|
||||
name,
|
||||
);
|
||||
|
||||
dict.insert_untagged("name", cmd_name);
|
||||
dict.insert_untagged(
|
||||
"description",
|
||||
get_data_by_key(&value, "usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
);
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
.as_string()
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
fn make_subcommands_table(
|
||||
subcommand_names: &mut Vec<String>,
|
||||
cmd_name: &str,
|
||||
registry: CommandRegistry,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
let (matching, not_matching) =
|
||||
subcommand_names.drain(..).partition(|subcommand_name| {
|
||||
subcommand_name.starts_with(&format!("{} ", cmd_name))
|
||||
});
|
||||
*subcommand_names = not_matching;
|
||||
Ok(if !matching.is_empty() {
|
||||
UntaggedValue::table(
|
||||
&(matching
|
||||
.into_iter()
|
||||
.map(|cmd_name: String| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
Ok(short_desc.into_value())
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?[..]),
|
||||
)
|
||||
.into_value(name)
|
||||
} else {
|
||||
UntaggedValue::nothing().into_value(name)
|
||||
})
|
||||
}
|
||||
|
||||
let iterator =
|
||||
command_names
|
||||
.into_iter()
|
||||
.map(move |cmd_name| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
short_desc.insert_value(
|
||||
"subcommands",
|
||||
make_subcommands_table(
|
||||
&mut subcommand_names,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?,
|
||||
);
|
||||
ReturnSuccess::value(short_desc.into_value())
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(iterator).to_output_stream())
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||
®istry,
|
||||
|
@ -1,22 +1,13 @@
|
||||
use crate::commands::group_by::group;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{columns_sorted, evaluate, map_max, reduce, t_sort};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
|
||||
pub struct Histogram;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HistogramArgs {
|
||||
column_name: Tagged<String>,
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Histogram {
|
||||
fn name(&self) -> &str {
|
||||
@ -25,13 +16,14 @@ impl WholeStreamCommand for Histogram {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("histogram")
|
||||
.required(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to graph by",
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"Use data at the column path given as valuator",
|
||||
None,
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::ColumnPath,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
}
|
||||
@ -57,8 +49,8 @@ impl WholeStreamCommand for Histogram {
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Get a histogram for the types of files, with frequency column named count",
|
||||
example: "ls | histogram type count",
|
||||
"Get a histogram for the types of files, with frequency column named percentage",
|
||||
example: "ls | histogram type percentage",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -76,181 +68,143 @@ pub async fn histogram(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (input, args) = args.evaluate_once(®istry).await?.parts();
|
||||
|
||||
let (HistogramArgs { column_name, rest }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let groups = group(&Some(column_name.clone()), &values, &name)?;
|
||||
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
|
||||
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
|
||||
let evaled = evaluate(&sorted, None, &name)?;
|
||||
let reduced = reduce(&evaled, None, &name)?;
|
||||
let maxima = map_max(&reduced, None, &name)?;
|
||||
let percents = percentages(&reduced, maxima, &name)?;
|
||||
let mut columns = args
|
||||
.positional_iter()
|
||||
.map(|c| c.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match percents {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let mut idx = 0;
|
||||
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let column = (*column_name).clone();
|
||||
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error(
|
||||
"Unable to load group count",
|
||||
"unabled to load group count",
|
||||
&name,
|
||||
);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(start),
|
||||
..
|
||||
} = datasets.get(0).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load dataset",
|
||||
"unabled to load dataset",
|
||||
&name,
|
||||
)
|
||||
})? {
|
||||
let start = start.clone();
|
||||
Ok(
|
||||
futures::stream::iter(start.into_iter().map(move |percentage| {
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let value: Tagged<String> = group_labels
|
||||
.get(idx)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load group labels",
|
||||
"unabled to load group labels",
|
||||
&name,
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
fact.insert_value(
|
||||
&column,
|
||||
UntaggedValue::string(value.item).into_value(value.tag),
|
||||
);
|
||||
|
||||
fact.insert_untagged(
|
||||
&count_column_name,
|
||||
UntaggedValue::int(count_values[idx]),
|
||||
);
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
|
||||
ref tag,
|
||||
} = percentage
|
||||
{
|
||||
let string = std::iter::repeat("*")
|
||||
.take(num.to_i32().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a number",
|
||||
"expected a number",
|
||||
tag,
|
||||
)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
fact.insert_untagged(
|
||||
&frequency_column_name,
|
||||
UntaggedValue::string(string),
|
||||
);
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
}
|
||||
_ => Ok(OutputStream::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let results: Value = match values {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let datasets: Vec<_> = datasets
|
||||
.iter()
|
||||
.map(|subsets| match subsets {
|
||||
Value {
|
||||
value: UntaggedValue::Table(data),
|
||||
..
|
||||
} => {
|
||||
let data = data
|
||||
.iter()
|
||||
.map(|d| match d {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
let max = match &max {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(maxima)),
|
||||
..
|
||||
} => maxima.clone(),
|
||||
_ => Zero::zero(),
|
||||
};
|
||||
|
||||
let n = (n * 100) / max;
|
||||
|
||||
UntaggedValue::int(n).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::int(0).into_value(&tag),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
UntaggedValue::Table(data).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::Table(vec![]).into_value(&tag),
|
||||
})
|
||||
.collect();
|
||||
|
||||
UntaggedValue::Table(datasets).into_value(&tag)
|
||||
}
|
||||
other => other.clone(),
|
||||
let evaluate_with = if let Some(path) = args.get("use") {
|
||||
Some(evaluator(path.as_column_path()?.item))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(results)
|
||||
let column_grouper = if !columns.is_empty() {
|
||||
match columns.remove(0).split_last() {
|
||||
Some((key, _)) => Some(key.as_string().tagged(&name)),
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let frequency_column_name = if columns.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else if let Some((key, _)) = columns[0].split_last() {
|
||||
key.as_string()
|
||||
} else {
|
||||
"frecuency".to_string()
|
||||
};
|
||||
|
||||
let column = if let Some(ref column) = column_grouper {
|
||||
column.clone()
|
||||
} else {
|
||||
"value".to_string().tagged(&name)
|
||||
};
|
||||
|
||||
let results = nu_data::utils::report(
|
||||
&UntaggedValue::table(&values).into_value(&name),
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: None,
|
||||
eval: &evaluate_with,
|
||||
},
|
||||
&name,
|
||||
)?;
|
||||
|
||||
let labels = results.labels.y.clone();
|
||||
let mut idx = 0;
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
results
|
||||
.percentages
|
||||
.table_entries()
|
||||
.map(move |value| {
|
||||
let values = value.table_entries().cloned().collect::<Vec<_>>();
|
||||
let occurrences = values.len();
|
||||
|
||||
(occurrences, values[occurrences - 1].clone())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(move |(occurrences, value)| {
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let column_value = labels
|
||||
.get(idx)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load group labels",
|
||||
"unabled to load group labels",
|
||||
&name,
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
|
||||
fact.insert_value(&column.item, column_value);
|
||||
fact.insert_untagged("occurrences", UntaggedValue::int(occurrences));
|
||||
|
||||
let percentage = format!(
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::str_::from::action(&value, &name, Some(2), true)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
|
||||
|
||||
let string = std::iter::repeat("*")
|
||||
.take(value.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
|
||||
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
|
||||
|
||||
idx += 1;
|
||||
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
}),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn splitter(
|
||||
by: Option<Tagged<String>>,
|
||||
) -> Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send> {
|
||||
match by {
|
||||
Some(column) => Box::new(move |_, row: &Value| {
|
||||
let key = &column;
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => nu_value_ext::as_string(&key),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
}),
|
||||
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(&row)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,10 +1,31 @@
|
||||
use crate::cli::History as HistoryFile;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_LOCATION: &str = "history.txt";
|
||||
|
||||
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||
let default_path = nu_data::config::user_data()
|
||||
.map(|mut p| {
|
||||
p.push(DEFAULT_LOCATION);
|
||||
p
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||
|
||||
config
|
||||
.var("history-path")
|
||||
.map_or(default_path.clone(), |custom_path| {
|
||||
match custom_path.as_string() {
|
||||
Ok(path) => PathBuf::from(path),
|
||||
Err(_) => default_path,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct History;
|
||||
|
||||
@ -32,9 +53,10 @@ impl WholeStreamCommand for History {
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||
let tag = args.call_info.name_tag;
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
let path = history_path(&config);
|
||||
let file = File::open(path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -74,7 +74,7 @@ async fn if_command(
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
|
||||
let (
|
||||
IfArgs {
|
||||
|
@ -1,10 +1,14 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Insert;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -26,11 +30,7 @@ impl WholeStreamCommand for Insert {
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column name to insert",
|
||||
)
|
||||
.required(
|
||||
"value",
|
||||
SyntaxShape::String,
|
||||
"the value to give the cell(s)",
|
||||
)
|
||||
.required("value", SyntaxShape::Any, "the value to give the cell(s)")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -46,26 +46,116 @@ impl WholeStreamCommand for Insert {
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
mut value: Arc<Value>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value = Arc::make_mut(&mut value);
|
||||
|
||||
let (InsertArgs { column, value }, input) = args.process(®istry).await?;
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag: block_tag,
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let values = stream.drain_vec().await;
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
let result = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to insert with"))?;
|
||||
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
match input {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.insert_data_at_column_path(&field, result) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
block_tag.clone(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
value => match input {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match scope.it.insert_data_at_column_path(&field, value.clone()) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
_ => match input.insert_data_at_column_path(&field, value.clone()) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
||||
let value = Arc::new(value);
|
||||
let column = Arc::new(column);
|
||||
|
||||
Ok(input
|
||||
.map(move |row| match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => Ok(ReturnSuccess::Value(
|
||||
row.insert_data_at_column_path(&column, value.clone())?,
|
||||
)),
|
||||
.then(move |input| {
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
let value = value.clone();
|
||||
let column = column.clone();
|
||||
|
||||
Value { tag, .. } => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
)),
|
||||
async {
|
||||
match process_row(scope, context, input, value, column).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
|
88
crates/nu-cli/src/commands/into_int.rs
Normal file
88
crates/nu-cli/src/commands/into_int.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
pub struct IntoInt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IntoIntArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IntoInt {
|
||||
fn name(&self) -> &str {
|
||||
"into-int"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into-int").rest(SyntaxShape::Any, "the values to into-int")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert value to integer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
into_int(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert filesize to integer",
|
||||
example: "echo 1kb | into-int $it | = $it / 1024",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn into_int(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (args, _): (IntoIntArgs, _) = args.process(®istry).await?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
} => match primitive_val {
|
||||
Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::int(size.to_bigint().expect("Conversion should never fail.")),
|
||||
tag,
|
||||
}))),
|
||||
Primitive::Int(_) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
}))),
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Could not convert int value",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
},
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(i))),
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IntoInt;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IntoInt {})
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -50,9 +50,16 @@ async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
if input.is_empty() {
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::boolean(true).into_value(name_tag),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
@ -72,7 +79,6 @@ async fn is_empty(
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
@ -106,14 +112,14 @@ async fn is_empty(
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default.clone()) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -11,11 +11,9 @@ pub struct Ls;
|
||||
pub struct LsArgs {
|
||||
pub path: Option<Tagged<PathBuf>>,
|
||||
pub all: bool,
|
||||
pub full: bool,
|
||||
pub long: bool,
|
||||
#[serde(rename = "short-names")]
|
||||
pub short_names: bool,
|
||||
#[serde(rename = "with-symlink-targets")]
|
||||
pub with_symlink_targets: bool,
|
||||
#[serde(rename = "du")]
|
||||
pub du: bool,
|
||||
}
|
||||
@ -33,25 +31,20 @@ impl WholeStreamCommand for Ls {
|
||||
SyntaxShape::Pattern,
|
||||
"a path to get the directory contents from",
|
||||
)
|
||||
.switch("all", "also show hidden files", Some('a'))
|
||||
.switch("all", "Show hidden files", Some('a'))
|
||||
.switch(
|
||||
"full",
|
||||
"list all available columns for each entry",
|
||||
Some('f'),
|
||||
"long",
|
||||
"List all available columns for each entry",
|
||||
Some('l'),
|
||||
)
|
||||
.switch(
|
||||
"short-names",
|
||||
"only print the file names and not the path",
|
||||
"Only print the file names and not the path",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
"with-symlink-targets",
|
||||
"display the paths to the target files that symlinks point to",
|
||||
Some('w'),
|
||||
)
|
||||
.switch(
|
||||
"du",
|
||||
"display the apparent directory size in place of the directory metadata size",
|
||||
"Display the apparent directory size in place of the directory metadata size",
|
||||
Some('d'),
|
||||
)
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
Block::extract(value)?
|
||||
}
|
||||
@ -321,7 +321,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
<$param_kind>::extract(&value)?
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use bigdecimal::{FromPrimitive, Zero};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{convert_number_to_u64, Number, Operator},
|
||||
Primitive, Signature, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use bigdecimal::FromPrimitive;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
@ -50,24 +53,68 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Get the average of a list of numbers",
|
||||
example: "echo [-50 100.0 25] | math avg",
|
||||
result: Some(vec![UntaggedValue::decimal(25).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
25.0,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn to_byte(value: &Value) -> Option<Value> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
|
||||
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
|
||||
num.clone(),
|
||||
))))
|
||||
.into_untagged_value(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let sum = reducer_for(Reduce::Summation);
|
||||
|
||||
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to big decimal",
|
||||
"could not convert to big decimal",
|
||||
&name.span,
|
||||
)
|
||||
ShellError::labeled_error("nothing to average", "nothing to average", &name.span)
|
||||
})?;
|
||||
|
||||
let total_rows = UntaggedValue::decimal(number);
|
||||
let total = sum(Value::zero(), values.to_vec())?;
|
||||
|
||||
let are_bytes = values
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
|
||||
})?
|
||||
.is_filesize();
|
||||
|
||||
let total = if are_bytes {
|
||||
to_byte(&sum(
|
||||
UntaggedValue::int(0).into_untagged_value(),
|
||||
values
|
||||
.to_vec()
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
|
||||
..
|
||||
} => UntaggedValue::int(*num as usize).into_untagged_value(),
|
||||
other => other.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to big decimal",
|
||||
"could not convert to big decimal",
|
||||
&name.span,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec())
|
||||
}?;
|
||||
|
||||
match total {
|
||||
Value {
|
||||
@ -75,7 +122,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -94,12 +141,34 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
)),
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Duration(duration)),
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Duration(duration));
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Duration(result))) => {
|
||||
Ok(UntaggedValue::duration(result).into_value(name))
|
||||
}
|
||||
Ok(_) => Err(ShellError::labeled_error(
|
||||
"could not calculate average of non-integer or unrelated types",
|
||||
"source",
|
||||
name,
|
||||
)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(other),
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -39,7 +39,7 @@ mod tests {
|
||||
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
||||
};
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::{decimal, int, table};
|
||||
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
|
||||
use nu_protocol::Value;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -98,17 +98,17 @@ mod tests {
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Values",
|
||||
values: vec![int(10), decimal(26.5), decimal(26.5)],
|
||||
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(21)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(table(&[decimal(26.5)])),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(table(&[decimal_from_float(26.5)])),
|
||||
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(63)),
|
||||
Ok(decimal(60.5)),
|
||||
Ok(decimal_from_float(60.5)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
@ -128,14 +128,14 @@ mod tests {
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Negative Values",
|
||||
values: vec![decimal(-13.5), decimal(-11.5), int(10)],
|
||||
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(decimal(-13.5)),
|
||||
Ok(decimal_from_float(-13.5)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(-11.5)),
|
||||
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
|
||||
Ok(decimal_from_float(-11.5)),
|
||||
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
|
||||
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(-15)),
|
||||
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
||||
@ -151,10 +151,10 @@ mod tests {
|
||||
],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
|
||||
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
|
||||
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
|
||||
Ok(row![
|
||||
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
||||
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
|
||||
@ -164,7 +164,7 @@ mod tests {
|
||||
"col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string"))
|
||||
]),
|
||||
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
|
||||
Ok(row!["col1".to_owned() => decimal(1.25), "col2".to_owned() => decimal(1.25)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(1.25), "col2".to_owned() => decimal_from_float(1.25)]),
|
||||
],
|
||||
},
|
||||
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved
|
||||
|
@ -41,7 +41,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Evalulate math in the pipeline",
|
||||
example: "echo '10 / 4' | math eval",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
2.5,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use bigdecimal::{FromPrimitive, Zero};
|
||||
use bigdecimal::FromPrimitive;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{convert_number_to_u64, Number, Operator},
|
||||
@ -50,7 +50,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Get the median of a list of numbers",
|
||||
example: "echo [3 8 9 12 12 15] | math median",
|
||||
result: Some(vec![UntaggedValue::decimal(10.5).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
10.5,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -130,7 +134,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
)
|
||||
})?;
|
||||
let total_rows = UntaggedValue::decimal(number);
|
||||
let total = sum(Value::zero(), values.to_vec())?;
|
||||
let total = sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec())?;
|
||||
|
||||
match total {
|
||||
Value {
|
||||
@ -138,7 +142,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -162,7 +166,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user