mirror of
https://github.com/nushell/nushell.git
synced 2025-08-20 03:59:04 +02:00
Compare commits
452 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df8775b48 | ||
|
|
e02b4f1443 | ||
|
|
194782215f | ||
|
|
df17d28c0f | ||
|
|
5f43c8f024 | ||
|
|
1a18734f9a | ||
|
|
4a70c1ff4f | ||
|
|
770e5d89f2 | ||
|
|
cfac8e84dd | ||
|
|
43d90c1745 | ||
|
|
38bdb053d2 | ||
|
|
95e61773a5 | ||
|
|
4e931fa73f | ||
|
|
2573441e28 | ||
|
|
5770b15270 | ||
|
|
6817b472d0 | ||
|
|
2b076369e0 | ||
|
|
973a8ee8f3 | ||
|
|
1159d3365a | ||
|
|
152ba32eb7 | ||
|
|
153320ef33 | ||
|
|
ff236da72c | ||
|
|
54326869e4 | ||
|
|
f14f4e39c5 | ||
|
|
a18b2702ca | ||
|
|
93410c470e | ||
|
|
5d945ef869 | ||
|
|
df07be6a42 | ||
|
|
3c32d4947c | ||
|
|
2ea5235aea | ||
|
|
c096f031ce | ||
|
|
ae1d4bdb4c | ||
|
|
0adf2accdd | ||
|
|
4201f48be5 | ||
|
|
b076e375ca | ||
|
|
2f1016d44f | ||
|
|
ddf9d61346 | ||
|
|
f0b7ab5ecc | ||
|
|
e4c6336bd4 | ||
|
|
66061192f8 | ||
|
|
b7bc4c1f80 | ||
|
|
a56abb6502 | ||
|
|
892a416211 | ||
|
|
f45adecd01 | ||
|
|
bd015e82dc | ||
|
|
cf43b74f26 | ||
|
|
18909ec14a | ||
|
|
ed243c88d2 | ||
|
|
cb7723f423 | ||
|
|
9dc88f8a95 | ||
|
|
0a439fe52f | ||
|
|
a8b65e35ec | ||
|
|
bd9e598bf0 | ||
|
|
75f8247af1 | ||
|
|
e8ec5027ff | ||
|
|
ebba89ea31 | ||
|
|
8388afc9d9 | ||
|
|
b133724b38 | ||
|
|
09429d08aa | ||
|
|
9b577b8679 | ||
|
|
7a595827f1 | ||
|
|
332e12ded0 | ||
|
|
a508e15efe | ||
|
|
a5b6bb6209 | ||
|
|
1882a32b83 | ||
|
|
798766b4b5 | ||
|
|
193c4cc6d5 | ||
|
|
422b6ca871 | ||
|
|
2b13ac3856 | ||
|
|
4c10351579 | ||
|
|
dd27aaef1b | ||
|
|
6eb4a0e87b | ||
|
|
15f3a545f0 | ||
|
|
365f76ad19 | ||
|
|
df2845a9b4 | ||
|
|
8453261211 | ||
|
|
1dc8f3300e | ||
|
|
10d4edc7af | ||
|
|
50cbf91bc5 | ||
|
|
d05f9b3b1e | ||
|
|
f5fad393d0 | ||
|
|
d19a5f4c2f | ||
|
|
04451af776 | ||
|
|
232aca76a4 | ||
|
|
0178b53289 | ||
|
|
e05e6b42fe | ||
|
|
dd79afb503 | ||
|
|
599bb9797d | ||
|
|
c355585112 | ||
|
|
45f32c9541 | ||
|
|
7528094e12 | ||
|
|
dcfa135ab9 | ||
|
|
e9bb4f25eb | ||
|
|
0f7a9bbd31 | ||
|
|
73e65df5f6 | ||
|
|
a63a5adafa | ||
|
|
2eb4f8d28a | ||
|
|
d9ae66791a | ||
|
|
2c5939dc7d | ||
|
|
3150e70fc7 | ||
|
|
c9ffd6afc0 | ||
|
|
986b427038 | ||
|
|
c973850571 | ||
|
|
5a725f9651 | ||
|
|
79cc725aff | ||
|
|
e2cbc4e853 | ||
|
|
b5a27f0ccb | ||
|
|
bdb12f4bff | ||
|
|
c9c29f9e4c | ||
|
|
32951f1161 | ||
|
|
56f85b3108 | ||
|
|
16f85f32a2 | ||
|
|
2ae2f2ea9d | ||
|
|
4696c9069b | ||
|
|
1ffbb66e64 | ||
|
|
8dc7b8a7cd | ||
|
|
666e6a7b57 | ||
|
|
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 | ||
|
|
2a084fc838 | ||
|
|
a36d2a1586 | ||
|
|
32b875ada9 | ||
|
|
aaed9c4e8a | ||
|
|
b9278bdfe1 | ||
|
|
6eb2c94209 | ||
|
|
7b1a15b223 | ||
|
|
836efd237c | ||
|
|
aad3cca793 | ||
|
|
6829ad7a30 | ||
|
|
1f0962eb08 | ||
|
|
c65acc174d | ||
|
|
2dea392e40 | ||
|
|
0c43a4d04b | ||
|
|
ebc2d40875 | ||
|
|
3432078e77 | ||
|
|
9e5170b3dc | ||
|
|
0ae7c5d836 | ||
|
|
d0712a00f4 | ||
|
|
5e722181cb | ||
|
|
ffe3e2c16b | ||
|
|
04e8aa31fe | ||
|
|
9d24b440bb | ||
|
|
d8594a62c2 | ||
|
|
dbe0effd67 | ||
|
|
b358804904 | ||
|
|
7b02604e6d | ||
|
|
6497421615 | ||
|
|
f26151e36d | ||
|
|
0f688d7da7 | ||
|
|
a04dfca63a | ||
|
|
72f6513d2a | ||
|
|
7c0a830d84 | ||
|
|
c299d207f7 | ||
|
|
42a1adf2e9 | ||
|
|
b4761f9d8a | ||
|
|
71e55541d7 | ||
|
|
5f1075544c | ||
|
|
0934410b38 | ||
|
|
17e6c53b62 | ||
|
|
80d2a7ee7a | ||
|
|
8fd22b61be | ||
|
|
e9313a61af | ||
|
|
f2c4d22739 | ||
|
|
8551e06d9e | ||
|
|
97cedeb324 | ||
|
|
07594222c0 | ||
|
|
7a207a673b | ||
|
|
78f13407e6 | ||
|
|
5a34744d8c | ||
|
|
0456f4a007 | ||
|
|
f3f40df4dd | ||
|
|
bdef5d7d72 | ||
|
|
8d03cf5b02 | ||
|
|
3ec0242960 | ||
|
|
0bc2e29f99 | ||
|
|
1bb6a2d9ed | ||
|
|
e848fc0bbe | ||
|
|
6820d70e7d | ||
|
|
f32ab696d3 | ||
|
|
e07a9e4ee7 | ||
|
|
6a89b1b010 | ||
|
|
b1b93931cb | ||
|
|
1e62a8fb6e | ||
|
|
ed6f337a48 | ||
|
|
b004236927 | ||
|
|
0fdb9ac5e2 | ||
|
|
28be39494c | ||
|
|
32f18536e1 | ||
|
|
34e1e6e426 | ||
|
|
c3ba1e476f | ||
|
|
a1a0710ee6 | ||
|
|
455b1ac294 | ||
|
|
b2e0dc5b77 | ||
|
|
d30c40b40e | ||
|
|
85d848dd7d | ||
|
|
74717582ac | ||
|
|
ee18f16378 | ||
|
|
9e82e5a2fa | ||
|
|
8ea2307815 | ||
|
|
bbc5a28fe9 | ||
|
|
04120e00e4 | ||
|
|
efd8a633f2 | ||
|
|
e75c44c95b | ||
|
|
0629c896eb | ||
|
|
eb02c773d0 | ||
|
|
e31e8d1550 | ||
|
|
8775991c2d | ||
|
|
de8e2841a0 | ||
|
|
5cafead4a4 | ||
|
|
180290f3a8 | ||
|
|
7813063c93 | ||
|
|
ba5d774fe1 | ||
|
|
7be49e43fd | ||
|
|
dcd2227201 | ||
|
|
2dd28c2909 | ||
|
|
0522023d4c | ||
|
|
9876169f5d | ||
|
|
ed10aafa6f | ||
|
|
bcddeb3c1f | ||
|
|
3f170c7fb8 | ||
|
|
8d91d151bf | ||
|
|
821d44af54 | ||
|
|
a30901ff7d | ||
|
|
94a1968a88 | ||
|
|
dffc9c9b1c | ||
|
|
8b3964f518 | ||
|
|
7fed9992c9 | ||
|
|
4e2a4236f8 | ||
|
|
05781607f4 | ||
|
|
6daec399e6 | ||
|
|
306dc89ede | ||
|
|
80ce8acf57 | ||
|
|
8dfc90a322 | ||
|
|
ad5e485594 | ||
|
|
60ed40f8bd | ||
|
|
a6228cab9e | ||
|
|
1857ac69d1 | ||
|
|
e33e80ab24 | ||
|
|
d18bc78e7c | ||
|
|
3b2a87b6d4 | ||
|
|
62c76be7ca | ||
|
|
733f93e673 | ||
|
|
2c88b2fae7 | ||
|
|
501da433d4 | ||
|
|
0e8a239ae1 | ||
|
|
bb08a221e2 | ||
|
|
0dbe347f84 | ||
|
|
72a21ad619 | ||
|
|
6372d2a18c | ||
|
|
4468947ad4 | ||
|
|
93144a0132 | ||
|
|
72f7406057 | ||
|
|
c56cbd0f6b | ||
|
|
1420cbafe4 | ||
|
|
053bd926ec | ||
|
|
d095cb91e4 | ||
|
|
e8476d8fbb | ||
|
|
7532618bdc | ||
|
|
e3e1e6f81b | ||
|
|
bce6f5a3e6 | ||
|
|
480600c465 | ||
|
|
89c737f456 | ||
|
|
4e83363dd3 | ||
|
|
de6d8738c4 | ||
|
|
853d7e7120 | ||
|
|
b0c30098e4 | ||
|
|
fcbaefed52 | ||
|
|
77e02ac1c1 | ||
|
|
088901b24f | ||
|
|
ed7a62bca3 | ||
|
|
6bfd8532e4 | ||
|
|
bc9cc75c8a | ||
|
|
53a6e9f0bd | ||
|
|
5f9de80d9b | ||
|
|
353b33be1b | ||
|
|
96d58094cf | ||
|
|
94aac0e8dd | ||
|
|
9f54d238ba | ||
|
|
778e497903 | ||
|
|
6914099e28 | ||
|
|
1b6f94b46c | ||
|
|
3d63901b3b | ||
|
|
eb1ada6115 | ||
|
|
831111edec | ||
|
|
29ea29261d | ||
|
|
ee835f75db | ||
|
|
bd7ac0d48e | ||
|
|
d7b1480ad0 | ||
|
|
86b316e930 | ||
|
|
a042f407c1 | ||
|
|
40673e4599 | ||
|
|
bcfb084d4c | ||
|
|
a1fd5ad128 | ||
|
|
fe6d96e996 | ||
|
|
e24e0242d1 | ||
|
|
c959dc1ee3 | ||
|
|
d82ce26b44 | ||
|
|
935a5f6b9e | ||
|
|
731aa6bbdd | ||
|
|
a268e825aa | ||
|
|
982f067d0e |
@@ -1,11 +1,20 @@
|
|||||||
trigger:
|
trigger:
|
||||||
- master
|
- main
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
linux-stable:
|
linux-stable:
|
||||||
image: ubuntu-18.04
|
image: ubuntu-18.04
|
||||||
style: 'unflagged'
|
style: 'unflagged'
|
||||||
|
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:
|
macos-stable:
|
||||||
image: macos-10.14
|
image: macos-10.14
|
||||||
style: 'unflagged'
|
style: 'unflagged'
|
||||||
@@ -34,6 +43,7 @@ steps:
|
|||||||
if [ -e /etc/debian_version ]
|
if [ -e /etc/debian_version ]
|
||||||
then
|
then
|
||||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||||
|
sudo npm install -g wasm-pack
|
||||||
fi
|
fi
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||||
@@ -41,23 +51,32 @@ steps:
|
|||||||
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
||||||
export PATH=$HOME/.cargo/bin:$PATH
|
export PATH=$HOME/.cargo/bin:$PATH
|
||||||
fi
|
fi
|
||||||
rustup update
|
# rustup update
|
||||||
rustc -Vv
|
# rustc -Vv
|
||||||
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||||
rustup component add rustfmt
|
# rustup component add rustfmt
|
||||||
displayName: Install Rust
|
displayName: Install Rust
|
||||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||||
condition: eq(variables['style'], 'unflagged')
|
condition: eq(variables['style'], 'unflagged')
|
||||||
displayName: Run tests
|
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')
|
condition: eq(variables['style'], 'unflagged')
|
||||||
displayName: Check clippy lints
|
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')
|
condition: eq(variables['style'], 'canary')
|
||||||
displayName: Run tests
|
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')
|
condition: eq(variables['style'], 'canary')
|
||||||
displayName: Check clippy lints
|
displayName: Check clippy lints
|
||||||
|
- 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
|
- bash: cargo fmt --all -- --check
|
||||||
condition: eq(variables['style'], 'fmt')
|
condition: eq(variables['style'], 'fmt')
|
||||||
displayName: Lint
|
displayName: Lint
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[build]
|
|
||||||
|
|
||||||
#rustflags = ["--cfg", "data_processing_primitives"]
|
|
||||||
6
.cargo/config.toml
Normal file
6
.cargo/config.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# use LLD as linker on Windows because it could be faster
|
||||||
|
# for full and incremental builds compared to default
|
||||||
|
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
#linker = "lld-link.exe"
|
||||||
|
rustflags = ["-C", "link-args=-stack:10000000"]
|
||||||
@@ -27,7 +27,7 @@ orbs:
|
|||||||
workflows:
|
workflows:
|
||||||
version: 2.0
|
version: 2.0
|
||||||
|
|
||||||
# This builds on all pull requests to test, and ignores master
|
# This builds on all pull requests to test, and ignores main
|
||||||
build_without_deploy:
|
build_without_deploy:
|
||||||
jobs:
|
jobs:
|
||||||
- docker/publish:
|
- docker/publish:
|
||||||
@@ -39,7 +39,7 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore:
|
ignore:
|
||||||
- master
|
- main
|
||||||
before_build:
|
before_build:
|
||||||
- pull_cache
|
- pull_cache
|
||||||
after_build:
|
after_build:
|
||||||
@@ -73,7 +73,7 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
tags:
|
tags:
|
||||||
only: /^v.*/
|
only: /^\d+\.\d+\.\d+$/
|
||||||
before_build:
|
before_build:
|
||||||
- run: docker pull quay.io/nushell/nu:latest
|
- run: docker pull quay.io/nushell/nu:latest
|
||||||
- run: docker pull quay.io/nushell/nu-base:latest
|
- run: docker pull quay.io/nushell/nu-base:latest
|
||||||
@@ -98,11 +98,11 @@ workflows:
|
|||||||
docker push quay.io/nushell/nu
|
docker push quay.io/nushell/nu
|
||||||
|
|
||||||
|
|
||||||
# publish devel to Docker Hub on merge to master (doesn't build --release)
|
# publish devel to Docker Hub on merge to main (doesn't build --release)
|
||||||
build_with_deploy_devel:
|
build_with_deploy_devel:
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
# Deploy devel tag on merge to master
|
# Deploy devel tag on merge to main
|
||||||
- docker/publish:
|
- docker/publish:
|
||||||
image: nushell/nu-base
|
image: nushell/nu-base
|
||||||
registry: quay.io
|
registry: quay.io
|
||||||
@@ -113,7 +113,7 @@ workflows:
|
|||||||
- pull_cache
|
- pull_cache
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: main
|
||||||
after_build:
|
after_build:
|
||||||
- run:
|
- run:
|
||||||
name: Build Multistage (smaller) container
|
name: Build Multistage (smaller) container
|
||||||
@@ -137,7 +137,7 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
- docker/publish:
|
- docker/publish:
|
||||||
image: nushell/nu-base
|
image: nushell/nu-base
|
||||||
|
|||||||
286
.github/workflows/release.yml
vendored
Normal file
286
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
name: Create Release Draft
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['[0-9]+.[0-9]+.[0-9]+*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
name: Build Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install libxcb
|
||||||
|
run: sudo apt-get install libxcb-composite0-dev
|
||||||
|
|
||||||
|
- name: Set up cargo
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --all --features=extra
|
||||||
|
|
||||||
|
- name: Create output directory
|
||||||
|
run: mkdir output
|
||||||
|
|
||||||
|
- name: Copy files to output
|
||||||
|
run: |
|
||||||
|
cp target/release/nu target/release/nu_plugin_* output/
|
||||||
|
cp README.build.txt output/README.txt
|
||||||
|
cp LICENSE output/LICENSE
|
||||||
|
rm output/*.d
|
||||||
|
rm output/nu_plugin_core_*
|
||||||
|
rm output/nu_plugin_extra_*
|
||||||
|
|
||||||
|
# Note: If OpenSSL changes, this path will need to be updated
|
||||||
|
- name: Copy OpenSSL to output
|
||||||
|
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: output/*
|
||||||
|
|
||||||
|
macos:
|
||||||
|
name: Build macOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up cargo
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --all --features=extra
|
||||||
|
|
||||||
|
- name: Create output directory
|
||||||
|
run: mkdir output
|
||||||
|
|
||||||
|
- name: Copy files to output
|
||||||
|
run: |
|
||||||
|
cp target/release/nu target/release/nu_plugin_* output/
|
||||||
|
cp README.build.txt output/README.txt
|
||||||
|
cp LICENSE output/LICENSE
|
||||||
|
rm output/*.d
|
||||||
|
rm output/nu_plugin_core_*
|
||||||
|
rm output/nu_plugin_extra_*
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: output/*
|
||||||
|
|
||||||
|
windows:
|
||||||
|
name: Build Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up cargo
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Add cargo-wix subcommand
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-wix
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --all --features=extra
|
||||||
|
|
||||||
|
- name: Create output directory
|
||||||
|
run: mkdir output
|
||||||
|
|
||||||
|
- name: Download Less Binary
|
||||||
|
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
|
||||||
|
|
||||||
|
- name: Download Less License
|
||||||
|
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
|
||||||
|
|
||||||
|
- name: Copy files to output
|
||||||
|
run: |
|
||||||
|
cp target\release\nu.exe output\
|
||||||
|
cp LICENSE output\
|
||||||
|
cp target\release\LICENSE-for-less.txt output\
|
||||||
|
rm target\release\nu_plugin_core_*.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\
|
||||||
|
# Note: If the version of `less.exe` needs to be changed, update this URL
|
||||||
|
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
|
||||||
|
# moved this stuff down to create wix after we download less
|
||||||
|
|
||||||
|
- name: Create msi with wix
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: wix
|
||||||
|
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
||||||
|
|
||||||
|
- name: Upload installer
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows-installer
|
||||||
|
path: target\wix\nushell-windows.msi
|
||||||
|
|
||||||
|
- name: Upload zip
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows-zip
|
||||||
|
path: output\*
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Publish Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- linux
|
||||||
|
- macos
|
||||||
|
- windows
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Determine Release Info
|
||||||
|
id: info
|
||||||
|
env:
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF##*/}
|
||||||
|
MAJOR=${VERSION%%.*}
|
||||||
|
MINOR=${VERSION%.*}
|
||||||
|
MINOR=${MINOR#*.}
|
||||||
|
PATCH=${VERSION##*.}
|
||||||
|
echo "::set-output name=version::${VERSION}"
|
||||||
|
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
|
||||||
|
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
||||||
|
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
||||||
|
echo "::set-output name=innerdir::nushell-${VERSION}"
|
||||||
|
|
||||||
|
- name: Create Release Draft
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ steps.info.outputs.version }} Release
|
||||||
|
draft: true
|
||||||
|
|
||||||
|
- name: Create Linux Directory
|
||||||
|
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Download Linux Artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Restore Linux File Modes
|
||||||
|
run: |
|
||||||
|
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||||
|
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
||||||
|
|
||||||
|
- name: Create Linux tarball
|
||||||
|
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
||||||
|
|
||||||
|
- name: Upload Linux Artifact
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||||
|
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||||
|
asset_content_type: application/gzip
|
||||||
|
|
||||||
|
- name: Create macOS Directory
|
||||||
|
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Download macOS Artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Restore macOS File Modes
|
||||||
|
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||||
|
|
||||||
|
- name: Create macOS Archive
|
||||||
|
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
||||||
|
|
||||||
|
- name: Upload macOS Artifact
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
||||||
|
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: Create Windows Directory
|
||||||
|
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Download Windows zip
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows-zip
|
||||||
|
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Show Windows Artifacts
|
||||||
|
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
|
- name: Create macOS Archive
|
||||||
|
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
||||||
|
|
||||||
|
- name: Upload Windows zip
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
||||||
|
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: Download Windows installer
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows-installer
|
||||||
|
path: ./
|
||||||
|
|
||||||
|
- name: Upload Windows installer
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./nushell-windows.msi
|
||||||
|
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
|
||||||
|
asset_content_type: applictaion/x-msi
|
||||||
@@ -68,9 +68,9 @@ members of the project's leadership.
|
|||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see
|
For answers to common questions about this code of conduct, see
|
||||||
https://www.contributor-covenant.org/faq
|
<https://www.contributor-covenant.org/faq>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
Welcome to nushell!
|
Welcome to nushell!
|
||||||
|
|
||||||
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
||||||
@@ -9,50 +11,65 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
|
|||||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||||
<!--WIP-->
|
<!--WIP-->
|
||||||
|
|
||||||
# Developing
|
## Developing
|
||||||
## Set up
|
|
||||||
|
### Set up
|
||||||
|
|
||||||
This is no different than other Rust projects.
|
This is no different than other Rust projects.
|
||||||
|
|
||||||
```shell
|
```bash
|
||||||
git clone https://github.com/nushell/nushell
|
git clone https://github.com/nushell/nushell
|
||||||
cd nushell
|
cd nushell
|
||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
Build and run Nushell:
|
- Build and run Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release && cargo run --release
|
cargo build --release && cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Run Clippy on Nushell:
|
- Build and run with extra features:
|
||||||
|
```shell
|
||||||
|
cargo build --release --features=extra && cargo run --release --features=extra
|
||||||
|
```
|
||||||
|
|
||||||
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo clippy --all --features=stable
|
cargo clippy --all --features=stable
|
||||||
```
|
```
|
||||||
|
|
||||||
Run all tests:
|
- Run all tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo test --all --features=stable
|
cargo test --all --features=stable
|
||||||
```
|
```
|
||||||
|
|
||||||
Run all tests for a specific command
|
- Run all tests for a specific command
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
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
|
```shell
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
```
|
```
|
||||||
|
|
||||||
Format the code in the project
|
- Format the code in the project
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Debugging Tips
|
||||||
|
|
||||||
|
- To view verbose logs when developing, enable the `trace` log level.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
|
||||||
|
```
|
||||||
|
|||||||
3767
Cargo.lock
generated
3767
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
191
Cargo.toml
191
Cargo.toml
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nu"
|
|
||||||
version = "0.15.0"
|
|
||||||
authors = ["The Nu Project Contributors"]
|
authors = ["The Nu Project Contributors"]
|
||||||
description = "A new type of shell"
|
|
||||||
license = "MIT"
|
|
||||||
edition = "2018"
|
|
||||||
readme = "README.md"
|
|
||||||
default-run = "nu"
|
default-run = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
description = "A new type of shell"
|
||||||
homepage = "https://www.nushell.sh"
|
|
||||||
documentation = "https://www.nushell.sh/book/"
|
documentation = "https://www.nushell.sh/book/"
|
||||||
|
edition = "2018"
|
||||||
exclude = ["images"]
|
exclude = ["images"]
|
||||||
|
homepage = "https://www.nushell.sh"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/nushell/nushell"
|
||||||
|
version = "0.21.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*/"]
|
members = ["crates/*/"]
|
||||||
@@ -18,66 +18,102 @@ members = ["crates/*/"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { version = "0.15.0", path = "./crates/nu-cli" }
|
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"}
|
||||||
nu-source = { version = "0.15.0", path = "./crates/nu-source" }
|
nu-data = {version = "0.21.0", path = "./crates/nu-data"}
|
||||||
nu-plugin = { version = "0.15.0", path = "./crates/nu-plugin" }
|
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"}
|
||||||
nu-protocol = { version = "0.15.0", path = "./crates/nu-protocol" }
|
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"}
|
||||||
nu-errors = { version = "0.15.0", path = "./crates/nu-errors" }
|
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"}
|
||||||
nu-parser = { version = "0.15.0", path = "./crates/nu-parser" }
|
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"}
|
||||||
nu-value-ext = { version = "0.15.0", path = "./crates/nu-value-ext" }
|
nu-source = {version = "0.21.0", path = "./crates/nu-source"}
|
||||||
nu_plugin_binaryview = { version = "0.15.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"}
|
||||||
nu_plugin_fetch = { version = "0.15.0", path = "./crates/nu_plugin_fetch", optional=true }
|
|
||||||
nu_plugin_inc = { version = "0.15.0", path = "./crates/nu_plugin_inc", optional=true }
|
|
||||||
nu_plugin_match = { version = "0.15.0", path = "./crates/nu_plugin_match", optional=true }
|
|
||||||
nu_plugin_post = { version = "0.15.0", path = "./crates/nu_plugin_post", optional=true }
|
|
||||||
nu_plugin_ps = { version = "0.15.0", path = "./crates/nu_plugin_ps", optional=true }
|
|
||||||
nu_plugin_start = { version = "0.15.0", path = "./crates/nu_plugin_start", optional=true }
|
|
||||||
nu_plugin_sys = { version = "0.15.0", path = "./crates/nu_plugin_sys", optional=true }
|
|
||||||
nu_plugin_textview = { version = "0.15.0", path = "./crates/nu_plugin_textview", optional=true }
|
|
||||||
nu_plugin_tree = { version = "0.15.0", path = "./crates/nu_plugin_tree", optional=true }
|
|
||||||
|
|
||||||
crossterm = { version = "0.17.5", optional = true }
|
nu_plugin_binaryview = {version = "0.21.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||||
semver = { version = "0.10.0", optional = true }
|
nu_plugin_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||||
syntect = { version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
|
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||||
url = { version = "2.1.1", optional = true }
|
nu_plugin_from_bson = {version = "0.21.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||||
|
nu_plugin_from_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||||
|
nu_plugin_inc = {version = "0.21.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||||
|
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true}
|
||||||
|
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true}
|
||||||
|
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||||
|
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||||
|
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true}
|
||||||
|
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||||
|
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||||
|
nu_plugin_to_bson = {version = "0.21.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
|
nu_plugin_to_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||||
|
nu_plugin_tree = {version = "0.21.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||||
|
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||||
|
|
||||||
clap = "2.33.1"
|
# Required to bootstrap the main binary
|
||||||
ctrlc = "3.1.4"
|
clap = "2.33.3"
|
||||||
dunce = "1.0.0"
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
log = "0.4.8"
|
log = "0.4.11"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { version = "0.15.0", path = "./crates/nu-test-support" }
|
dunce = "1.0.1"
|
||||||
|
nu-test-support = {version = "0.21.0", path = "./crates/nu-test-support"}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
toml = "0.5.6"
|
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
|
||||||
nu-build = { version = "0.15.0", path = "./crates/nu-build" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sys", "ps", "textview", "inc"]
|
ctrlc-support = ["nu-cli/ctrlc"]
|
||||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
|
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||||
|
git-support = ["nu-cli/git2"]
|
||||||
|
ptree-support = ["nu-cli/ptree"]
|
||||||
|
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||||
|
rustyline-support = ["nu-cli/rustyline-support"]
|
||||||
|
term-support = ["nu-cli/term"]
|
||||||
|
uuid-support = ["nu-cli/uuid_crate"]
|
||||||
|
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||||
|
|
||||||
# Default
|
default = [
|
||||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
"sys",
|
||||||
sys = ["nu_plugin_sys"]
|
"ps",
|
||||||
ps = ["nu_plugin_ps"]
|
"textview",
|
||||||
inc = ["semver", "nu_plugin_inc"]
|
"inc",
|
||||||
|
"git-support",
|
||||||
|
"directories-support",
|
||||||
|
"ctrlc-support",
|
||||||
|
"which-support",
|
||||||
|
"ptree-support",
|
||||||
|
"term-support",
|
||||||
|
"uuid-support",
|
||||||
|
"rustyline-support",
|
||||||
|
"match",
|
||||||
|
"post",
|
||||||
|
"fetch",
|
||||||
|
"rich-benchmark",
|
||||||
|
]
|
||||||
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"]
|
||||||
|
stable = ["default"]
|
||||||
|
|
||||||
# Stable
|
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||||
binaryview = ["nu_plugin_binaryview"]
|
|
||||||
|
trace = ["nu-parser/trace"]
|
||||||
|
|
||||||
|
# Stable (Default)
|
||||||
fetch = ["nu_plugin_fetch"]
|
fetch = ["nu_plugin_fetch"]
|
||||||
|
inc = ["nu_plugin_inc"]
|
||||||
match = ["nu_plugin_match"]
|
match = ["nu_plugin_match"]
|
||||||
post = ["nu_plugin_post"]
|
post = ["nu_plugin_post"]
|
||||||
trace = ["nu-parser/trace"]
|
ps = ["nu_plugin_ps"]
|
||||||
tree = ["nu_plugin_tree"]
|
sys = ["nu_plugin_sys"]
|
||||||
start = ["nu_plugin_start"]
|
textview = ["nu_plugin_textview"]
|
||||||
|
|
||||||
|
# Extra
|
||||||
|
binaryview = ["nu_plugin_binaryview"]
|
||||||
|
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||||
|
chart = ["nu_plugin_chart"]
|
||||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||||
starship-prompt = ["nu-cli/starship-prompt"]
|
s3 = ["nu_plugin_s3"]
|
||||||
|
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||||
|
start = ["nu_plugin_start"]
|
||||||
trash-support = ["nu-cli/trash-support"]
|
trash-support = ["nu-cli/trash-support"]
|
||||||
|
tree = ["nu_plugin_tree"]
|
||||||
|
xpath = ["nu_plugin_xpath"]
|
||||||
|
|
||||||
# Core plugins that ship with `cargo install nu` by default
|
# Core plugins that ship with `cargo install nu` by default
|
||||||
# Currently, Cargo limits us to installing only one binary
|
# Currently, Cargo limits us to installing only one binary
|
||||||
@@ -102,37 +138,58 @@ name = "nu_plugin_core_sys"
|
|||||||
path = "src/plugins/nu_plugin_core_sys.rs"
|
path = "src/plugins/nu_plugin_core_sys.rs"
|
||||||
required-features = ["sys"]
|
required-features = ["sys"]
|
||||||
|
|
||||||
# Stable plugins
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu_plugin_stable_fetch"
|
name = "nu_plugin_core_fetch"
|
||||||
path = "src/plugins/nu_plugin_stable_fetch.rs"
|
path = "src/plugins/nu_plugin_core_fetch.rs"
|
||||||
required-features = ["fetch"]
|
required-features = ["fetch"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu_plugin_stable_binaryview"
|
name = "nu_plugin_core_match"
|
||||||
path = "src/plugins/nu_plugin_stable_binaryview.rs"
|
path = "src/plugins/nu_plugin_core_match.rs"
|
||||||
required-features = ["binaryview"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "nu_plugin_stable_match"
|
|
||||||
path = "src/plugins/nu_plugin_stable_match.rs"
|
|
||||||
required-features = ["match"]
|
required-features = ["match"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu_plugin_stable_post"
|
name = "nu_plugin_core_post"
|
||||||
path = "src/plugins/nu_plugin_stable_post.rs"
|
path = "src/plugins/nu_plugin_core_post.rs"
|
||||||
required-features = ["post"]
|
required-features = ["post"]
|
||||||
|
|
||||||
|
# Extra plugins
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu_plugin_stable_tree"
|
name = "nu_plugin_extra_binaryview"
|
||||||
path = "src/plugins/nu_plugin_stable_tree.rs"
|
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"]
|
required-features = ["tree"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu_plugin_stable_start"
|
name = "nu_plugin_extra_start"
|
||||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||||
required-features = ["start"]
|
required-features = ["start"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_s3"
|
||||||
|
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||||
|
required-features = ["s3"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_chart_bar"
|
||||||
|
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
|
||||||
|
required-features = ["chart"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_chart_line"
|
||||||
|
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||||
|
required-features = ["chart"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_xpath"
|
||||||
|
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||||
|
required-features = ["xpath"]
|
||||||
|
|
||||||
# Main nu binary
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
|
|||||||
1
README.build.txt
Normal file
1
README.build.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Nu will look for the plugins in your PATH on startup. While nu will have some functionality without them, for full functionality you'll need to copy them into your path so they can be loaded.
|
||||||
137
README.md
137
README.md
@@ -1,17 +1,19 @@
|
|||||||
|
# README
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||||
[](https://crates.io/crates/nu)
|
[](https://crates.io/crates/nu)
|
||||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
||||||
[](https://discord.gg/NtAbbGn)
|
[](https://discord.gg/NtAbbGn)
|
||||||
[](https://changelog.com/podcast/363)
|
[](https://changelog.com/podcast/363)
|
||||||
|
[](https://twitter.com/nu_shell)
|
||||||
|
|
||||||
|
## Nu Shell
|
||||||
# Nu Shell
|
|
||||||
|
|
||||||
A new type of shell.
|
A new type of shell.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Status
|
## Status
|
||||||
|
|
||||||
This project has reached a minimum-viable product level of quality.
|
This project has reached a minimum-viable product level of quality.
|
||||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
||||||
@@ -21,7 +23,7 @@ Its design is also subject to change as it matures.
|
|||||||
Nu comes with a set of built-in commands (listed below).
|
Nu comes with a set of built-in commands (listed below).
|
||||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
||||||
|
|
||||||
# Learning more
|
## Learning more
|
||||||
|
|
||||||
There are a few good resources to learn about Nu.
|
There are a few good resources to learn about Nu.
|
||||||
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
||||||
@@ -38,9 +40,9 @@ Try it in Gitpod.
|
|||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||||
|
|
||||||
# Installation
|
## Installation
|
||||||
|
|
||||||
## Local
|
### Local
|
||||||
|
|
||||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||||
|
|
||||||
@@ -49,28 +51,28 @@ To build Nu, you will need to use the **latest stable (1.41 or later)** version
|
|||||||
Required dependencies:
|
Required dependencies:
|
||||||
|
|
||||||
* pkg-config and libssl (only needed on Linux)
|
* 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:
|
Optional dependencies:
|
||||||
|
|
||||||
* To use Nu with all possible optional features enabled, you'll also need the following:
|
* 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`):
|
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||||
|
|
||||||
```
|
```bash
|
||||||
cargo install nu
|
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:
|
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
|
### Docker
|
||||||
|
|
||||||
### Quickstart
|
#### Quickstart
|
||||||
|
|
||||||
Want to try Nu right away? Execute the following to get started.
|
Want to try Nu right away? Execute the following to get started.
|
||||||
|
|
||||||
@@ -78,14 +80,14 @@ Want to try Nu right away? Execute the following to get started.
|
|||||||
docker run -it quay.io/nushell/nu:latest
|
docker run -it quay.io/nushell/nu:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Guide
|
#### Guide
|
||||||
|
|
||||||
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
||||||
on Quay.io. Pulling a container would come down to:
|
on Quay.io. Pulling a container would come down to:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker pull quay.io/nushell/nu
|
docker pull quay.io/nushell/nu
|
||||||
$ docker pull quay.io/nushell/nu-base
|
docker pull quay.io/nushell/nu-base
|
||||||
```
|
```
|
||||||
|
|
||||||
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
||||||
@@ -95,41 +97,41 @@ Optionally, you can also build the containers locally using the [dockerfiles pro
|
|||||||
To build the base image:
|
To build the base image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||||
```
|
```
|
||||||
|
|
||||||
And then to build the smaller container (using a Multistage build):
|
And then to build the smaller container (using a Multistage build):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker build -f docker/Dockerfile -t nushell/nu .
|
docker build -f docker/Dockerfile -t nushell/nu .
|
||||||
```
|
```
|
||||||
|
|
||||||
Either way, you can run either container as follows:
|
Either way, you can run either container as follows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker run -it nushell/nu-base
|
docker run -it nushell/nu-base
|
||||||
$ docker run -it nushell/nu
|
docker run -it nushell/nu
|
||||||
/> exit
|
/> exit
|
||||||
```
|
```
|
||||||
|
|
||||||
The second container is a bit smaller if the size is important to you.
|
The second container is a bit smaller if the size is important to you.
|
||||||
|
|
||||||
## Packaging status
|
### Packaging status
|
||||||
|
|
||||||
[](https://repology.org/project/nushell/versions)
|
[](https://repology.org/project/nushell/versions)
|
||||||
|
|
||||||
### Fedora
|
#### Fedora
|
||||||
|
|
||||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
||||||
|
|
||||||
# Philosophy
|
## Philosophy
|
||||||
|
|
||||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
||||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
||||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||||
|
|
||||||
## Pipelines
|
### Pipelines
|
||||||
|
|
||||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||||
@@ -138,40 +140,40 @@ Additionally, commands can output structured data (you can think of this as a th
|
|||||||
Commands that work in the pipeline fit into one of three categories:
|
Commands that work in the pipeline fit into one of three categories:
|
||||||
|
|
||||||
* Commands that produce a stream (eg, `ls`)
|
* Commands that produce a stream (eg, `ls`)
|
||||||
* Commands that filter a stream (eg, `where type == "Directory"`)
|
* Commands that filter a stream (eg, `where type == "Dir"`)
|
||||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||||
|
|
||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> ls | where type == "Dir" | autoview
|
> ls | where type == "Dir" | autoview
|
||||||
───┬────────┬──────┬────────┬──────────────
|
───┬────────┬──────┬───────┬──────────────
|
||||||
# │ name │ type │ size │ modified
|
# │ name │ type │ size │ modified
|
||||||
───┼────────┼──────┼────────┼──────────────
|
───┼────────┼──────┼───────┼──────────────
|
||||||
0 │ assets │ Dir │ 4.1 KB │ 1 week ago
|
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||||
1 │ crates │ Dir │ 4.1 KB │ 4 days ago
|
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||||
2 │ debian │ Dir │ 4.1 KB │ 1 week ago
|
2 │ debian │ Dir │ 352 B │ 5 months ago
|
||||||
3 │ docker │ Dir │ 4.1 KB │ 1 week ago
|
3 │ docker │ Dir │ 288 B │ 3 months ago
|
||||||
4 │ docs │ Dir │ 4.1 KB │ 1 week ago
|
4 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||||
5 │ images │ Dir │ 4.1 KB │ 1 week ago
|
5 │ images │ Dir │ 160 B │ 5 months ago
|
||||||
6 │ src │ Dir │ 4.1 KB │ 1 week ago
|
6 │ src │ Dir │ 128 B │ 1 day ago
|
||||||
7 │ target │ Dir │ 4.1 KB │ 23 hours ago
|
7 │ target │ Dir │ 160 B │ 5 days ago
|
||||||
8 │ tests │ Dir │ 4.1 KB │ 1 week ago
|
8 │ tests │ Dir │ 192 B │ 3 months ago
|
||||||
───┴────────┴──────┴────────┴──────────────
|
───┴────────┴──────┴───────┴──────────────
|
||||||
```
|
```
|
||||||
|
|
||||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
||||||
We could have also written the above:
|
We could have also written the above:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> ls | where type == Directory
|
> ls | where type == Dir
|
||||||
```
|
```
|
||||||
|
|
||||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||||
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
||||||
|
|
||||||
```text
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> ps | where cpu > 0
|
> ps | where cpu > 0
|
||||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
||||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
# │ pid │ name │ status │ cpu │ mem │ virtual
|
||||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
||||||
@@ -183,16 +185,16 @@ For example, we could use the built-in `ps` command as well to get a list of the
|
|||||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
||||||
```
|
```
|
||||||
|
|
||||||
## Opening files
|
### Opening files
|
||||||
|
|
||||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
||||||
For example, you can load a .toml file as structured data and explore it:
|
For example, you can load a .toml file as structured data and explore it:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> open Cargo.toml
|
> open Cargo.toml
|
||||||
────────────────────┬───────────────────────────
|
────────────────────┬───────────────────────────
|
||||||
bin │ [table 18 rows]
|
bin │ [table 18 rows]
|
||||||
build-dependencies │ [row nu-build serde toml]
|
build-dependencies │ [row serde toml]
|
||||||
dependencies │ [row 29 columns]
|
dependencies │ [row 29 columns]
|
||||||
dev-dependencies │ [row nu-test-support]
|
dev-dependencies │ [row nu-test-support]
|
||||||
features │ [row 19 columns]
|
features │ [row 19 columns]
|
||||||
@@ -203,8 +205,8 @@ For example, you can load a .toml file as structured data and explore it:
|
|||||||
|
|
||||||
We can pipeline this into a command that gets the contents of one of the columns:
|
We can pipeline this into a command that gets the contents of one of the columns:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package
|
> open Cargo.toml | get package
|
||||||
───────────────┬────────────────────────────────────
|
───────────────┬────────────────────────────────────
|
||||||
authors │ [table 1 rows]
|
authors │ [table 1 rows]
|
||||||
default-run │ nu
|
default-run │ nu
|
||||||
@@ -217,31 +219,31 @@ We can pipeline this into a command that gets the contents of one of the columns
|
|||||||
name │ nu
|
name │ nu
|
||||||
readme │ README.md
|
readme │ README.md
|
||||||
repository │ https://github.com/nushell/nushell
|
repository │ https://github.com/nushell/nushell
|
||||||
version │ 0.14.1
|
version │ 0.15.1
|
||||||
───────────────┴────────────────────────────────────
|
───────────────┴────────────────────────────────────
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we can use commands outside of Nu once we have the data we want:
|
Finally, we can use commands outside of Nu once we have the data we want:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
|
> open Cargo.toml | get package.version | echo $it
|
||||||
0.14.1
|
0.15.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||||
|
|
||||||
## Configuration
|
### Configuration
|
||||||
|
|
||||||
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).
|
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 edit_mode "vi"
|
||||||
> config --set [path $nu.path]
|
> config set path $nu.path
|
||||||
```
|
```
|
||||||
|
|
||||||
## Shells
|
### Shells
|
||||||
|
|
||||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
||||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
||||||
@@ -252,7 +254,7 @@ Once you're done with a shell, you can `exit` it and remove it from the ring buf
|
|||||||
|
|
||||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
Finally, to get a list of all the current shells, you can use the `shells` command.
|
||||||
|
|
||||||
## Plugins
|
### Plugins
|
||||||
|
|
||||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
||||||
This allows you to extend nu for your needs.
|
This allows you to extend nu for your needs.
|
||||||
@@ -264,7 +266,7 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
|||||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||||
|
|
||||||
# Goals
|
## Goals
|
||||||
|
|
||||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||||
|
|
||||||
@@ -278,11 +280,11 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
|
|||||||
|
|
||||||
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||||
|
|
||||||
# Commands
|
## Commands
|
||||||
|
|
||||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
|
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
|
||||||
|
|
||||||
# Progress
|
## Progress
|
||||||
|
|
||||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||||
|
|
||||||
@@ -303,11 +305,14 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
|||||||
| Completions | | X | | | | Completions are currently barebones, at best
|
| Completions | | X | | | | Completions are currently barebones, at best
|
||||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||||
|
|
||||||
# Contributing
|
## Current Roadmap
|
||||||
|
|
||||||
|
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](CONTRIBUTING.md) for details.
|
See [Contributing](CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
# License
|
## License
|
||||||
|
|
||||||
The project is made available under the MIT license. See the `LICENSE` file for more information.
|
The project is made available under the MIT license. See the `LICENSE` file for more information.
|
||||||
|
|
||||||
|
|||||||
60
TODO.md
60
TODO.md
@@ -1,60 +0,0 @@
|
|||||||
This pattern is extremely repetitive and can be abstracted:
|
|
||||||
|
|
||||||
```rs
|
|
||||||
let args = args.evaluate_once(registry)?;
|
|
||||||
let tag = args.name_tag();
|
|
||||||
let input = args.input;
|
|
||||||
|
|
||||||
let stream = async_stream! {
|
|
||||||
let values: Vec<Value> = input.values.collect().await;
|
|
||||||
|
|
||||||
let mut concat_string = String::new();
|
|
||||||
let mut latest_tag: Option<Tag> = None;
|
|
||||||
|
|
||||||
for value in values {
|
|
||||||
latest_tag = Some(value_tag.clone());
|
|
||||||
let value_span = value.tag.span;
|
|
||||||
|
|
||||||
match &value.value {
|
|
||||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
|
||||||
concat_string.push_str(&s);
|
|
||||||
concat_string.push_str("\n");
|
|
||||||
}
|
|
||||||
_ => yield Err(ShellError::labeled_error_with_secondary(
|
|
||||||
"Expected a string from pipeline",
|
|
||||||
"requires string input",
|
|
||||||
name_span,
|
|
||||||
"value originates from here",
|
|
||||||
value_span,
|
|
||||||
)),
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Mandatory and Optional in parse_command
|
|
||||||
|
|
||||||
trace_remaining?
|
|
||||||
|
|
||||||
select_fields and select_fields take unnecessary Tag
|
|
||||||
|
|
||||||
Value#value should be Value#untagged
|
|
||||||
|
|
||||||
Unify dictionary building, probably around a macro
|
|
||||||
|
|
||||||
sys plugin in own crate
|
|
||||||
|
|
||||||
textview in own crate
|
|
||||||
|
|
||||||
Combine atomic and atomic_parse in parser
|
|
||||||
|
|
||||||
at_end_possible_ws needs to be comment and separator sensitive
|
|
||||||
|
|
||||||
Eliminate unnecessary `nodes` parser
|
|
||||||
|
|
||||||
#[derive(HasSpan)]
|
|
||||||
|
|
||||||
Figure out a solution for the duplication in stuff like NumberShape vs. NumberExpressionShape
|
|
||||||
|
|
||||||
use `struct Expander` from signature.rs
|
|
||||||
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]
|
|
||||||
name = "nu-build"
|
|
||||||
version = "0.15.0"
|
|
||||||
authors = ["The Nu Project Contributors"]
|
|
||||||
edition = "2018"
|
|
||||||
description = "Core build system for nushell"
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
serde_json = "1.0.53"
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
@@ -1,114 +1,129 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nu-cli"
|
|
||||||
version = "0.15.0"
|
|
||||||
authors = ["The Nu Project Contributors"]
|
authors = ["The Nu Project Contributors"]
|
||||||
description = "CLI for nushell"
|
description = "CLI for nushell"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
name = "nu-cli"
|
||||||
|
version = "0.21.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-source = { version = "0.15.0", path = "../nu-source" }
|
nu-data = {version = "0.21.0", path = "../nu-data"}
|
||||||
nu-plugin = { version = "0.15.0", path = "../nu-plugin" }
|
nu-errors = {version = "0.21.0", path = "../nu-errors"}
|
||||||
nu-protocol = { version = "0.15.0", path = "../nu-protocol" }
|
nu-parser = {version = "0.21.0", path = "../nu-parser"}
|
||||||
nu-errors = { version = "0.15.0", path = "../nu-errors" }
|
nu-plugin = {version = "0.21.0", path = "../nu-plugin"}
|
||||||
nu-parser = { version = "0.15.0", path = "../nu-parser" }
|
nu-protocol = {version = "0.21.0", path = "../nu-protocol"}
|
||||||
nu-value-ext = { version = "0.15.0", path = "../nu-value-ext" }
|
nu-source = {version = "0.21.0", path = "../nu-source"}
|
||||||
nu-test-support = { version = "0.15.0", path = "../nu-test-support" }
|
nu-table = {version = "0.21.0", path = "../nu-table"}
|
||||||
|
nu-test-support = {version = "0.21.0", path = "../nu-test-support"}
|
||||||
|
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"}
|
||||||
|
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
app_dirs = "1.2.1"
|
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
async-trait = "0.1.31"
|
async-trait = "0.1.40"
|
||||||
directories = "2.0.2"
|
base64 = "0.12.3"
|
||||||
async-stream = "0.2"
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
base64 = "0.12.1"
|
byte-unit = "4.0.9"
|
||||||
bigdecimal = { version = "0.1.2", features = ["serde"] }
|
bytes = "0.5.6"
|
||||||
bson = { version = "0.14.1", features = ["decimal128"] }
|
calamine = "0.16.1"
|
||||||
byte-unit = "3.1.3"
|
chrono = {version = "0.4.15", features = ["serde"]}
|
||||||
bytes = "0.5.4"
|
clap = "2.33.3"
|
||||||
calamine = "0.16"
|
codespan-reporting = "0.9.5"
|
||||||
cfg-if = "0.1"
|
csv = "1.1.3"
|
||||||
chrono = { version = "0.4.11", features = ["serde"] }
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
clap = "2.33.1"
|
|
||||||
csv = "1.1"
|
|
||||||
ctrlc = "3.1.4"
|
|
||||||
derive-new = "0.5.8"
|
derive-new = "0.5.8"
|
||||||
dirs = "2.0.2"
|
directories = {version = "3.0.1", optional = true}
|
||||||
dunce = "1.0.0"
|
dirs = {version = "3.0.1", optional = true}
|
||||||
|
dtparse = "1.2.0"
|
||||||
|
dunce = "1.0.1"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
fs_extra = "1.2.0"
|
||||||
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
futures-util = "0.3.5"
|
futures-util = "0.3.5"
|
||||||
futures_codec = "0.4"
|
futures_codec = "0.4.1"
|
||||||
getset = "0.1.1"
|
getset = "0.1.1"
|
||||||
git2 = { version = "0.13.6", default_features = false }
|
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
hex = "0.4"
|
heim = {version = "0.1.0-beta.3", optional = true}
|
||||||
htmlescape = "0.3.1"
|
htmlescape = "0.3.1"
|
||||||
ical = "0.6.*"
|
ical = "0.6.0"
|
||||||
ichwh = "0.3.4"
|
ichwh = {version = "0.3.4", optional = true}
|
||||||
indexmap = { version = "1.3.2", features = ["serde-1"] }
|
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
codespan-reporting = "0.9.4"
|
log = "0.4.11"
|
||||||
log = "0.4.8"
|
meval = "0.2.0"
|
||||||
meval = "0.2"
|
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||||
natural = "0.5.0"
|
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
num-traits = "0.2.12"
|
||||||
num-traits = "0.2.11"
|
parking_lot = "0.11.0"
|
||||||
parking_lot = "0.10.2"
|
|
||||||
pin-utils = "0.1.0"
|
pin-utils = "0.1.0"
|
||||||
pretty-hex = "0.1.1"
|
pretty-hex = "0.2.0"
|
||||||
pretty_env_logger = "0.4.0"
|
ptree = {version = "0.3.0", optional = true}
|
||||||
prettytable-rs = "0.8.0"
|
|
||||||
ptree = {version = "0.2" }
|
|
||||||
query_interface = "0.3.5"
|
query_interface = "0.3.5"
|
||||||
rand = "0.7"
|
quick-xml = "0.18.1"
|
||||||
regex = "1"
|
rand = "0.7.3"
|
||||||
roxmltree = "0.11.0"
|
regex = "1.3.9"
|
||||||
rustyline = "6.2.0"
|
roxmltree = "0.13.0"
|
||||||
serde = { version = "1.0.110", 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-hjson = "0.9.1"
|
||||||
serde_bytes = "0.11.4"
|
serde_bytes = "0.11.5"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.53"
|
serde_json = "1.0.57"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8.13"
|
||||||
|
sha2 = "0.9.1"
|
||||||
shellexpand = "2.0.0"
|
shellexpand = "2.0.0"
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
|
sxd-document = "0.3.2"
|
||||||
|
sxd-xpath = "0.4.2"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
term = "0.5.2"
|
term = {version = "0.6.1", optional = true}
|
||||||
|
term_size = "0.3.2"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
typetag = "0.1.4"
|
unicode-segmentation = "1.6.0"
|
||||||
umask = "1.0.0"
|
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||||
unicode-xid = "0.2.0"
|
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||||
which = "3"
|
which = {version = "4.0.2", optional = true}
|
||||||
|
zip = {version = "0.5.7", optional = true}
|
||||||
|
|
||||||
trash = { version = "1.0.1", optional = true }
|
Inflector = "0.11"
|
||||||
clipboard = { version = "0.5", optional = true }
|
clipboard = {version = "0.5.0", optional = true}
|
||||||
starship = { version = "0.41.3", optional = true }
|
encoding_rs = "0.8.24"
|
||||||
rayon = "1.3.0"
|
rayon = "1.4.0"
|
||||||
|
trash = {version = "1.1.1", optional = true}
|
||||||
|
url = "2.1.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
umask = "1.0.0"
|
||||||
users = "0.10.0"
|
users = "0.10.0"
|
||||||
|
|
||||||
|
# TODO this will be possible with new dependency resolver
|
||||||
|
# (currently on nightly behind -Zfeatures=itarget):
|
||||||
|
# https://github.com/rust-lang/cargo/issues/7914
|
||||||
|
#[target.'cfg(not(windows))'.dependencies]
|
||||||
|
#num-format = {version = "0.4", features = ["with-system-locale"]}
|
||||||
|
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.23.1"
|
|
||||||
features = ["bundled", "blob"]
|
features = ["bundled", "blob"]
|
||||||
|
optional = true
|
||||||
|
version = "0.24.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
nu-build = { version = "0.15.0", path = "../nu-build" }
|
git2 = {version = "0.13.11", optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9.2"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
stable = []
|
|
||||||
starship-prompt = ["starship"]
|
|
||||||
clipboard-cli = ["clipboard"]
|
clipboard-cli = ["clipboard"]
|
||||||
|
rich-benchmark = ["heim"]
|
||||||
|
rustyline-support = ["rustyline"]
|
||||||
|
stable = []
|
||||||
trash-support = ["trash"]
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,44 +5,51 @@ mod from_delimited_data;
|
|||||||
mod to_delimited_data;
|
mod to_delimited_data;
|
||||||
|
|
||||||
pub(crate) mod alias;
|
pub(crate) mod alias;
|
||||||
|
pub(crate) mod ansi;
|
||||||
pub(crate) mod append;
|
pub(crate) mod append;
|
||||||
pub(crate) mod args;
|
pub(crate) mod args;
|
||||||
|
pub(crate) mod autoenv;
|
||||||
|
pub(crate) mod autoenv_trust;
|
||||||
|
pub(crate) mod autoenv_untrust;
|
||||||
pub(crate) mod autoview;
|
pub(crate) mod autoview;
|
||||||
pub(crate) mod average;
|
pub(crate) mod benchmark;
|
||||||
pub(crate) mod build_string;
|
pub(crate) mod build_string;
|
||||||
pub(crate) mod cal;
|
pub(crate) mod cal;
|
||||||
pub(crate) mod calc;
|
|
||||||
pub(crate) mod cd;
|
pub(crate) mod cd;
|
||||||
|
pub(crate) mod char_;
|
||||||
|
pub(crate) mod chart;
|
||||||
pub(crate) mod classified;
|
pub(crate) mod classified;
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard-cli")]
|
||||||
pub(crate) mod clip;
|
pub(crate) mod clip;
|
||||||
pub(crate) mod command;
|
pub(crate) mod command;
|
||||||
pub(crate) mod compact;
|
pub(crate) mod compact;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
|
pub(crate) mod constants;
|
||||||
pub(crate) mod count;
|
pub(crate) mod count;
|
||||||
pub(crate) mod cp;
|
pub(crate) mod cp;
|
||||||
pub(crate) mod date;
|
pub(crate) mod date;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
|
pub(crate) mod describe;
|
||||||
|
pub(crate) mod do_;
|
||||||
pub(crate) mod drop;
|
pub(crate) mod drop;
|
||||||
pub(crate) mod du;
|
pub(crate) mod du;
|
||||||
pub(crate) mod each;
|
pub(crate) mod each;
|
||||||
pub(crate) mod echo;
|
pub(crate) mod echo;
|
||||||
|
pub(crate) mod empty;
|
||||||
pub(crate) mod enter;
|
pub(crate) mod enter;
|
||||||
#[allow(unused)]
|
pub(crate) mod every;
|
||||||
pub(crate) mod evaluate_by;
|
pub(crate) mod exec;
|
||||||
pub(crate) mod exit;
|
pub(crate) mod exit;
|
||||||
pub(crate) mod first;
|
pub(crate) mod first;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
pub(crate) mod from;
|
pub(crate) mod from;
|
||||||
pub(crate) mod from_bson;
|
|
||||||
pub(crate) mod from_csv;
|
pub(crate) mod from_csv;
|
||||||
pub(crate) mod from_eml;
|
pub(crate) mod from_eml;
|
||||||
pub(crate) mod from_ics;
|
pub(crate) mod from_ics;
|
||||||
pub(crate) mod from_ini;
|
pub(crate) mod from_ini;
|
||||||
pub(crate) mod from_json;
|
pub(crate) mod from_json;
|
||||||
pub(crate) mod from_ods;
|
pub(crate) mod from_ods;
|
||||||
pub(crate) mod from_sqlite;
|
|
||||||
pub(crate) mod from_ssv;
|
pub(crate) mod from_ssv;
|
||||||
pub(crate) mod from_toml;
|
pub(crate) mod from_toml;
|
||||||
pub(crate) mod from_tsv;
|
pub(crate) mod from_tsv;
|
||||||
@@ -58,31 +65,30 @@ pub(crate) mod headers;
|
|||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
pub(crate) mod histogram;
|
pub(crate) mod histogram;
|
||||||
pub(crate) mod history;
|
pub(crate) mod history;
|
||||||
|
pub(crate) mod if_;
|
||||||
pub(crate) mod insert;
|
pub(crate) mod insert;
|
||||||
pub(crate) mod is_empty;
|
pub(crate) mod into_int;
|
||||||
pub(crate) mod keep;
|
pub(crate) mod keep;
|
||||||
pub(crate) mod keep_until;
|
|
||||||
pub(crate) mod keep_while;
|
|
||||||
pub(crate) mod last;
|
pub(crate) mod last;
|
||||||
pub(crate) mod lines;
|
pub(crate) mod lines;
|
||||||
pub(crate) mod ls;
|
pub(crate) mod ls;
|
||||||
#[allow(unused)]
|
pub(crate) mod math;
|
||||||
pub(crate) mod map_max_by;
|
|
||||||
pub(crate) mod merge;
|
pub(crate) mod merge;
|
||||||
pub(crate) mod mkdir;
|
pub(crate) mod mkdir;
|
||||||
pub(crate) mod mv;
|
pub(crate) mod move_;
|
||||||
pub(crate) mod next;
|
pub(crate) mod next;
|
||||||
pub(crate) mod nth;
|
pub(crate) mod nth;
|
||||||
|
pub(crate) mod nu;
|
||||||
pub(crate) mod open;
|
pub(crate) mod open;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
|
pub(crate) mod path;
|
||||||
pub(crate) mod pivot;
|
pub(crate) mod pivot;
|
||||||
pub(crate) mod plugin;
|
|
||||||
pub(crate) mod prepend;
|
pub(crate) mod prepend;
|
||||||
pub(crate) mod prev;
|
pub(crate) mod prev;
|
||||||
pub(crate) mod pwd;
|
pub(crate) mod pwd;
|
||||||
|
pub(crate) mod random;
|
||||||
pub(crate) mod range;
|
pub(crate) mod range;
|
||||||
#[allow(unused)]
|
pub(crate) mod reduce;
|
||||||
pub(crate) mod reduce_by;
|
|
||||||
pub(crate) mod reject;
|
pub(crate) mod reject;
|
||||||
pub(crate) mod rename;
|
pub(crate) mod rename;
|
||||||
pub(crate) mod reverse;
|
pub(crate) mod reverse;
|
||||||
@@ -95,33 +101,27 @@ pub(crate) mod shells;
|
|||||||
pub(crate) mod shuffle;
|
pub(crate) mod shuffle;
|
||||||
pub(crate) mod size;
|
pub(crate) mod size;
|
||||||
pub(crate) mod skip;
|
pub(crate) mod skip;
|
||||||
pub(crate) mod skip_until;
|
pub(crate) mod sleep;
|
||||||
pub(crate) mod skip_while;
|
|
||||||
pub(crate) mod sort_by;
|
pub(crate) mod sort_by;
|
||||||
pub(crate) mod split;
|
pub(crate) mod split;
|
||||||
pub(crate) mod split_by;
|
pub(crate) mod split_by;
|
||||||
pub(crate) mod str_;
|
pub(crate) mod str_;
|
||||||
pub(crate) mod sum;
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) mod t_sort_by;
|
|
||||||
pub(crate) mod table;
|
pub(crate) mod table;
|
||||||
pub(crate) mod tags;
|
pub(crate) mod tags;
|
||||||
pub(crate) mod to;
|
pub(crate) mod to;
|
||||||
pub(crate) mod to_bson;
|
|
||||||
pub(crate) mod to_csv;
|
pub(crate) mod to_csv;
|
||||||
pub(crate) mod to_html;
|
pub(crate) mod to_html;
|
||||||
pub(crate) mod to_json;
|
pub(crate) mod to_json;
|
||||||
pub(crate) mod to_md;
|
pub(crate) mod to_md;
|
||||||
pub(crate) mod to_sqlite;
|
|
||||||
pub(crate) mod to_toml;
|
pub(crate) mod to_toml;
|
||||||
pub(crate) mod to_tsv;
|
pub(crate) mod to_tsv;
|
||||||
pub(crate) mod to_url;
|
pub(crate) mod to_url;
|
||||||
|
pub(crate) mod to_xml;
|
||||||
pub(crate) mod to_yaml;
|
pub(crate) mod to_yaml;
|
||||||
pub(crate) mod trim;
|
|
||||||
pub(crate) mod uniq;
|
pub(crate) mod uniq;
|
||||||
pub(crate) mod update;
|
pub(crate) mod update;
|
||||||
|
pub(crate) mod url_;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
pub(crate) mod what;
|
|
||||||
pub(crate) mod where_;
|
pub(crate) mod where_;
|
||||||
pub(crate) mod which_;
|
pub(crate) mod which_;
|
||||||
pub(crate) mod with_env;
|
pub(crate) mod with_env;
|
||||||
@@ -134,45 +134,55 @@ pub(crate) use command::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use alias::Alias;
|
pub(crate) use alias::Alias;
|
||||||
pub(crate) use append::Append;
|
pub(crate) use ansi::Ansi;
|
||||||
pub(crate) use average::Average;
|
pub(crate) use append::Command as Append;
|
||||||
|
pub(crate) use autoenv::Autoenv;
|
||||||
|
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||||
|
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||||
|
pub(crate) use benchmark::Benchmark;
|
||||||
pub(crate) use build_string::BuildString;
|
pub(crate) use build_string::BuildString;
|
||||||
pub(crate) use cal::Cal;
|
pub(crate) use cal::Cal;
|
||||||
pub(crate) use calc::Calc;
|
pub(crate) use char_::Char;
|
||||||
|
pub(crate) use chart::Chart;
|
||||||
pub(crate) use compact::Compact;
|
pub(crate) use compact::Compact;
|
||||||
pub(crate) use config::Config;
|
pub(crate) use config::{
|
||||||
|
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||||
|
};
|
||||||
pub(crate) use count::Count;
|
pub(crate) use count::Count;
|
||||||
pub(crate) use cp::Cpy;
|
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 debug::Debug;
|
||||||
pub(crate) use default::Default;
|
pub(crate) use default::Default;
|
||||||
|
pub(crate) use describe::Describe;
|
||||||
|
pub(crate) use do_::Do;
|
||||||
pub(crate) use drop::Drop;
|
pub(crate) use drop::Drop;
|
||||||
pub(crate) use du::Du;
|
pub(crate) use du::Du;
|
||||||
pub(crate) use each::Each;
|
pub(crate) use each::Each;
|
||||||
|
pub(crate) use each::EachGroup;
|
||||||
|
pub(crate) use each::EachWindow;
|
||||||
pub(crate) use echo::Echo;
|
pub(crate) use echo::Echo;
|
||||||
pub(crate) use is_empty::IsEmpty;
|
pub(crate) use empty::Command as Empty;
|
||||||
pub(crate) use update::Update;
|
pub(crate) use if_::If;
|
||||||
|
pub(crate) use nu::NuPlugin;
|
||||||
|
pub(crate) use update::Command as Update;
|
||||||
pub(crate) mod kill;
|
pub(crate) mod kill;
|
||||||
pub(crate) use kill::Kill;
|
pub(crate) use kill::Kill;
|
||||||
pub(crate) mod clear;
|
pub(crate) mod clear;
|
||||||
pub(crate) use clear::Clear;
|
pub(crate) use clear::Clear;
|
||||||
pub(crate) mod touch;
|
pub(crate) mod touch;
|
||||||
pub(crate) use enter::Enter;
|
pub(crate) use enter::Enter;
|
||||||
#[allow(unused_imports)]
|
pub(crate) use every::Every;
|
||||||
pub(crate) use evaluate_by::EvaluateBy;
|
pub(crate) use exec::Exec;
|
||||||
pub(crate) use exit::Exit;
|
pub(crate) use exit::Exit;
|
||||||
pub(crate) use first::First;
|
pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use format::Format;
|
||||||
pub(crate) use from::From;
|
pub(crate) use from::From;
|
||||||
pub(crate) use from_bson::FromBSON;
|
|
||||||
pub(crate) use from_csv::FromCSV;
|
pub(crate) use from_csv::FromCSV;
|
||||||
pub(crate) use from_eml::FromEML;
|
pub(crate) use from_eml::FromEML;
|
||||||
pub(crate) use from_ics::FromIcs;
|
pub(crate) use from_ics::FromIcs;
|
||||||
pub(crate) use from_ini::FromINI;
|
pub(crate) use from_ini::FromINI;
|
||||||
pub(crate) use from_json::FromJSON;
|
pub(crate) use from_json::FromJSON;
|
||||||
pub(crate) use from_ods::FromODS;
|
pub(crate) use from_ods::FromODS;
|
||||||
pub(crate) use from_sqlite::FromDB;
|
|
||||||
pub(crate) use from_sqlite::FromSQLite;
|
|
||||||
pub(crate) use from_ssv::FromSSV;
|
pub(crate) use from_ssv::FromSSV;
|
||||||
pub(crate) use from_toml::FromTOML;
|
pub(crate) use from_toml::FromTOML;
|
||||||
pub(crate) use from_tsv::FromTSV;
|
pub(crate) use from_tsv::FromTSV;
|
||||||
@@ -183,35 +193,42 @@ pub(crate) use from_xml::FromXML;
|
|||||||
pub(crate) use from_yaml::FromYAML;
|
pub(crate) use from_yaml::FromYAML;
|
||||||
pub(crate) use from_yaml::FromYML;
|
pub(crate) use from_yaml::FromYML;
|
||||||
pub(crate) use get::Get;
|
pub(crate) use get::Get;
|
||||||
pub(crate) use group_by::GroupBy;
|
pub(crate) use group_by::Command as GroupBy;
|
||||||
pub(crate) use group_by_date::GroupByDate;
|
pub(crate) use group_by_date::GroupByDate;
|
||||||
pub(crate) use headers::Headers;
|
pub(crate) use headers::Headers;
|
||||||
pub(crate) use help::Help;
|
pub(crate) use help::Help;
|
||||||
pub(crate) use histogram::Histogram;
|
pub(crate) use histogram::Histogram;
|
||||||
pub(crate) use history::History;
|
pub(crate) use history::History;
|
||||||
pub(crate) use insert::Insert;
|
pub(crate) use insert::Command as Insert;
|
||||||
pub(crate) use keep::Keep;
|
pub(crate) use into_int::IntoInt;
|
||||||
pub(crate) use keep_until::KeepUntil;
|
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||||
pub(crate) use keep_while::KeepWhile;
|
|
||||||
pub(crate) use last::Last;
|
pub(crate) use last::Last;
|
||||||
pub(crate) use lines::Lines;
|
pub(crate) use lines::Lines;
|
||||||
pub(crate) use ls::Ls;
|
pub(crate) use ls::Ls;
|
||||||
#[allow(unused_imports)]
|
pub(crate) use math::{
|
||||||
pub(crate) use map_max_by::MapMaxBy;
|
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||||
|
MathStddev, MathSummation, MathVariance,
|
||||||
|
};
|
||||||
pub(crate) use merge::Merge;
|
pub(crate) use merge::Merge;
|
||||||
pub(crate) use mkdir::Mkdir;
|
pub(crate) use mkdir::Mkdir;
|
||||||
pub(crate) use mv::Move;
|
pub(crate) use move_::{Move, MoveColumn, Mv};
|
||||||
pub(crate) use next::Next;
|
pub(crate) use next::Next;
|
||||||
pub(crate) use nth::Nth;
|
pub(crate) use nth::Nth;
|
||||||
pub(crate) use open::Open;
|
pub(crate) use open::Open;
|
||||||
pub(crate) use parse::Parse;
|
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 pivot::Pivot;
|
||||||
pub(crate) use prepend::Prepend;
|
pub(crate) use prepend::Prepend;
|
||||||
pub(crate) use prev::Previous;
|
pub(crate) use prev::Previous;
|
||||||
pub(crate) use pwd::Pwd;
|
pub(crate) use pwd::Pwd;
|
||||||
|
#[cfg(feature = "uuid_crate")]
|
||||||
|
pub(crate) use random::RandomUUID;
|
||||||
|
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
||||||
pub(crate) use range::Range;
|
pub(crate) use range::Range;
|
||||||
#[allow(unused_imports)]
|
pub(crate) use reduce::Reduce;
|
||||||
pub(crate) use reduce_by::ReduceBy;
|
|
||||||
pub(crate) use reject::Reject;
|
pub(crate) use reject::Reject;
|
||||||
pub(crate) use rename::Rename;
|
pub(crate) use rename::Rename;
|
||||||
pub(crate) use reverse::Reverse;
|
pub(crate) use reverse::Reverse;
|
||||||
@@ -222,41 +239,70 @@ pub(crate) use select::Select;
|
|||||||
pub(crate) use shells::Shells;
|
pub(crate) use shells::Shells;
|
||||||
pub(crate) use shuffle::Shuffle;
|
pub(crate) use shuffle::Shuffle;
|
||||||
pub(crate) use size::Size;
|
pub(crate) use size::Size;
|
||||||
pub(crate) use skip::Skip;
|
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
|
||||||
pub(crate) use skip_until::SkipUntil;
|
pub(crate) use sleep::Sleep;
|
||||||
pub(crate) use skip_while::SkipWhile;
|
|
||||||
pub(crate) use sort_by::SortBy;
|
pub(crate) use sort_by::SortBy;
|
||||||
pub(crate) use split::Split;
|
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||||
pub(crate) use split::SplitColumn;
|
|
||||||
pub(crate) use split::SplitRow;
|
|
||||||
pub(crate) use split_by::SplitBy;
|
pub(crate) use split_by::SplitBy;
|
||||||
pub(crate) use str_::{
|
pub(crate) use str_::{
|
||||||
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
|
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||||
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||||
|
StrReverse, StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring,
|
||||||
|
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||||
};
|
};
|
||||||
pub(crate) use sum::Sum;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use t_sort_by::TSortBy;
|
|
||||||
pub(crate) use table::Table;
|
pub(crate) use table::Table;
|
||||||
pub(crate) use tags::Tags;
|
pub(crate) use tags::Tags;
|
||||||
pub(crate) use to::To;
|
pub(crate) use to::To;
|
||||||
pub(crate) use to_bson::ToBSON;
|
|
||||||
pub(crate) use to_csv::ToCSV;
|
pub(crate) use to_csv::ToCSV;
|
||||||
pub(crate) use to_html::ToHTML;
|
pub(crate) use to_html::ToHTML;
|
||||||
pub(crate) use to_json::ToJSON;
|
pub(crate) use to_json::ToJSON;
|
||||||
pub(crate) use to_md::ToMarkdown;
|
pub(crate) use to_md::ToMarkdown;
|
||||||
pub(crate) use to_sqlite::ToDB;
|
|
||||||
pub(crate) use to_sqlite::ToSQLite;
|
|
||||||
pub(crate) use to_toml::ToTOML;
|
pub(crate) use to_toml::ToTOML;
|
||||||
pub(crate) use to_tsv::ToTSV;
|
pub(crate) use to_tsv::ToTSV;
|
||||||
pub(crate) use to_url::ToURL;
|
pub(crate) use to_url::ToURL;
|
||||||
|
pub(crate) use to_xml::ToXML;
|
||||||
pub(crate) use to_yaml::ToYAML;
|
pub(crate) use to_yaml::ToYAML;
|
||||||
pub(crate) use touch::Touch;
|
pub(crate) use touch::Touch;
|
||||||
pub(crate) use trim::Trim;
|
|
||||||
pub(crate) use uniq::Uniq;
|
pub(crate) use uniq::Uniq;
|
||||||
|
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||||
pub(crate) use version::Version;
|
pub(crate) use version::Version;
|
||||||
pub(crate) use what::What;
|
|
||||||
pub(crate) use where_::Where;
|
pub(crate) use where_::Where;
|
||||||
pub(crate) use which_::Which;
|
pub(crate) use which_::Which;
|
||||||
pub(crate) use with_env::WithEnv;
|
pub(crate) use with_env::WithEnv;
|
||||||
pub(crate) use wrap::Wrap;
|
pub(crate) use wrap::Wrap;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::commands::whole_stream_command;
|
||||||
|
use crate::examples::{test_anchors, test_examples};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
|
fn commands() -> Vec<Command> {
|
||||||
|
vec![
|
||||||
|
whole_stream_command(Append),
|
||||||
|
whole_stream_command(GroupBy),
|
||||||
|
whole_stream_command(Insert),
|
||||||
|
whole_stream_command(Update),
|
||||||
|
whole_stream_command(Empty),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
for cmd in commands() {
|
||||||
|
test_examples(cmd)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tracks_metadata() -> Result<(), ShellError> {
|
||||||
|
for cmd in commands() {
|
||||||
|
test_anchors(cmd)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::data::config;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use nu_data::config;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_parser::SignatureRegistry;
|
||||||
|
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
||||||
|
UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct Alias;
|
pub struct Alias;
|
||||||
|
|
||||||
@@ -15,6 +19,7 @@ pub struct AliasArgs {
|
|||||||
pub name: Tagged<String>,
|
pub name: Tagged<String>,
|
||||||
pub args: Vec<Value>,
|
pub args: Vec<Value>,
|
||||||
pub block: Block,
|
pub block: Block,
|
||||||
|
pub infer: Option<bool>,
|
||||||
pub save: Option<bool>,
|
pub save: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +38,7 @@ impl WholeStreamCommand for Alias {
|
|||||||
SyntaxShape::Block,
|
SyntaxShape::Block,
|
||||||
"the block to run as the body of the alias",
|
"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'))
|
.switch("save", "save the alias to your config", Some('s'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +51,7 @@ impl WholeStreamCommand for Alias {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
// args.process(registry, alias)?.run()
|
alias(args, registry).await
|
||||||
alias(args, registry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -65,29 +70,45 @@ impl WholeStreamCommand for Alias {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// <<<<<<< HEAD
|
pub async fn alias(
|
||||||
// pub fn alias(alias_args: AliasArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
|
args: CommandArgs,
|
||||||
// =======
|
registry: &CommandRegistry,
|
||||||
pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
|
||||||
let mut raw_input = args.raw_input.clone();
|
let mut raw_input = args.raw_input.clone();
|
||||||
let (AliasArgs { name, args: list, block, save}, ctx) = args.process(®istry).await?;
|
let (
|
||||||
|
AliasArgs {
|
||||||
|
name,
|
||||||
|
args: list,
|
||||||
|
block,
|
||||||
|
infer,
|
||||||
|
save,
|
||||||
|
},
|
||||||
|
_ctx,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
let mut processed_args: Vec<String> = vec![];
|
let mut processed_args: Vec<String> = vec![];
|
||||||
|
|
||||||
if let Some(true) = save {
|
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
|
// process the alias to remove the --save flag
|
||||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||||
let right_brace = raw_input.rfind('}').unwrap_or(raw_input.len());
|
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||||
let mut left = raw_input[..left_brace].replace("--save", "").replace("-s", "");
|
let left = raw_input[..left_brace]
|
||||||
let mut right = raw_input[right_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("-si", "-i")
|
||||||
|
.replace("-s ", "")
|
||||||
|
.replace("-is", "-i");
|
||||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||||
|
|
||||||
// create a value from raw_input alias
|
// create a value from raw_input alias
|
||||||
let alias: Value = raw_input.trim().to_string().into();
|
let alias: Value = raw_input.trim().to_string().into();
|
||||||
let alias_start = raw_input.find("[").unwrap_or(0); // used to check if the same alias already exists
|
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||||
|
|
||||||
// add to startup if alias doesn't exist and replce if it does
|
// add to startup if alias doesn't exist and replce if it does
|
||||||
match result.get_mut("startup") {
|
match result.get_mut("startup") {
|
||||||
@@ -104,7 +125,7 @@ pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut table = UntaggedValue::table(&[alias]);
|
let table = UntaggedValue::table(&[alias]);
|
||||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,23 +136,216 @@ pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
|||||||
if let Ok(string) = item.as_string() {
|
if let Ok(string) = item.as_string() {
|
||||||
processed_args.push(format!("${}", string));
|
processed_args.push(format!("${}", string));
|
||||||
} else {
|
} else {
|
||||||
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
|
return Err(ShellError::labeled_error(
|
||||||
|
"Expected a string",
|
||||||
|
"expected a string",
|
||||||
|
item.tag(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), processed_args, block.clone()))
|
|
||||||
|
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()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Alias;
|
use super::Alias;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Alias {})
|
Ok(test_examples(Alias {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
146
crates/nu-cli/src/commands/ansi.rs
Normal file
146
crates/nu-cli/src/commands/ansi.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use ansi_term::Color;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct Ansi;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AnsiArgs {
|
||||||
|
color: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Ansi {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ansi"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ansi").required(
|
||||||
|
"color",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the name of the color to use or 'reset' to reset the color",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Output ANSI codes to change color"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Change color to green",
|
||||||
|
example: r#"ansi green"#,
|
||||||
|
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Reset the color",
|
||||||
|
example: r#"ansi reset"#,
|
||||||
|
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||||
|
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||||
|
result: Some(vec![Value::from(
|
||||||
|
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||||
|
)]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let color_string = color.as_string()?;
|
||||||
|
|
||||||
|
let ansi_code = str_to_ansi_color(color_string);
|
||||||
|
|
||||||
|
if let Some(output) = ansi_code {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(color.tag()),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Unknown color",
|
||||||
|
"unknown color",
|
||||||
|
color.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn str_to_ansi_color(s: String) -> Option<String> {
|
||||||
|
match s.as_str() {
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Ansi;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Ansi {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AppendArgs {
|
struct Arguments {
|
||||||
row: Value,
|
row: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Append;
|
pub struct Command;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Append {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"append"
|
"append"
|
||||||
}
|
}
|
||||||
@@ -34,11 +34,18 @@ impl WholeStreamCommand for Append {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
let (Arguments { mut row }, input) = args.process(registry).await?;
|
||||||
|
|
||||||
let eos = futures::stream::iter(vec![row]);
|
let input: Vec<Value> = input.collect().await;
|
||||||
|
|
||||||
Ok(input.chain(eos).to_output_stream())
|
if let Some(first) = input.get(0) {
|
||||||
|
row.tag = first.tag();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
futures::stream::iter(input.into_iter().chain(vec![row]).map(ReturnSuccess::value))
|
||||||
|
.to_output_stream(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -54,15 +61,3 @@ impl WholeStreamCommand for Append {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Append;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Append {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
92
crates/nu-cli/src/commands/autoenv.rs
Normal file
92
crates/nu-cli/src/commands/autoenv.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
pub struct Autoenv;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||||
|
pub struct Trusted {
|
||||||
|
pub files: IndexMap<String, Vec<u8>>,
|
||||||
|
}
|
||||||
|
impl Trusted {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Trusted {
|
||||||
|
files: IndexMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn file_is_trusted(nu_env_file: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
|
||||||
|
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
|
||||||
|
let nufile = std::fs::canonicalize(nu_env_file)?;
|
||||||
|
|
||||||
|
let trusted = read_trusted()?;
|
||||||
|
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_trusted() -> Result<Trusted, ShellError> {
|
||||||
|
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||||
|
|
||||||
|
let mut file = std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.open(config_path)
|
||||||
|
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
|
||||||
|
let mut doc = String::new();
|
||||||
|
file.read_to_string(&mut doc)?;
|
||||||
|
|
||||||
|
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||||
|
Ok(allowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Autoenv {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"autoenv"
|
||||||
|
}
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
|
||||||
|
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
|
||||||
|
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."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv")
|
||||||
|
}
|
||||||
|
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(&Autoenv, ®istry))
|
||||||
|
.into_value(Tag::unknown()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Example .nu-env file",
|
||||||
|
example: r#"cat .nu-env
|
||||||
|
[env]
|
||||||
|
mykey = "myvalue"
|
||||||
|
|
||||||
|
[scriptvars]
|
||||||
|
myscript = "echo myval"
|
||||||
|
|
||||||
|
[scripts]
|
||||||
|
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||||
|
exitscripts = ["touch bye.txt"]"#,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
83
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
83
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use super::autoenv::read_trusted;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::SyntaxShape;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
pub struct AutoenvTrust;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for AutoenvTrust {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"autoenv trust"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv trust").optional("dir", SyntaxShape::String, "Directory to allow")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Trust a .nu-env file in the current or given directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let file_to_trust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||||
|
tag: _,
|
||||||
|
}) => {
|
||||||
|
let mut dir = fs::canonicalize(path)?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut dir = fs::canonicalize(std::env::current_dir()?)?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = std::fs::read(&file_to_trust)?;
|
||||||
|
|
||||||
|
let filename = file_to_trust.to_string_lossy().to_string();
|
||||||
|
let mut allowed = read_trusted()?;
|
||||||
|
allowed
|
||||||
|
.files
|
||||||
|
.insert(filename, Sha256::digest(&content).as_slice().to_vec());
|
||||||
|
|
||||||
|
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||||
|
let tomlstr = toml::to_string(&allowed).map_err(|_| {
|
||||||
|
ShellError::untagged_runtime_error("Couldn't serialize allowed dirs to nu-env.toml")
|
||||||
|
})?;
|
||||||
|
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(".nu-env trusted!").into_value(tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn is_binary(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Allow .nu-env file in current directory",
|
||||||
|
example: "autoenv trust",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Allow .nu-env file in directory foo",
|
||||||
|
example: "autoenv trust foo",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
107
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
107
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use super::autoenv::Trusted;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::SyntaxShape;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
use std::io::Read;
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
pub struct AutoenvUnTrust;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for AutoenvUnTrust {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"autoenv untrust"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv untrust").optional(
|
||||||
|
"dir",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Directory to disallow",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Untrust a .nu-env file in the current or given directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let file_to_untrust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||||
|
tag: _,
|
||||||
|
}) => {
|
||||||
|
let mut dir = fs::canonicalize(path)?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut dir = std::env::current_dir()?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||||
|
|
||||||
|
let mut file = match std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.open(config_path.clone())
|
||||||
|
{
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::untagged_runtime_error(
|
||||||
|
"Couldn't open nu-env.toml",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut doc = String::new();
|
||||||
|
file.read_to_string(&mut doc)?;
|
||||||
|
|
||||||
|
let mut allowed: Trusted = toml::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||||
|
|
||||||
|
let file_to_untrust = file_to_untrust.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
if allowed.files.remove(&file_to_untrust).is_none() {
|
||||||
|
return
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"No .nu-env file to untrust in the given directory. Is it missing, or already untrusted?",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tomlstr = toml::to_string(&allowed).map_err(|_| {
|
||||||
|
ShellError::untagged_runtime_error("Couldn't serialize allowed dirs to nu-env.toml")
|
||||||
|
})?;
|
||||||
|
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(".nu-env untrusted!").into_value(tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn is_binary(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Disallow .nu-env file in current directory",
|
||||||
|
example: "autoenv untrust",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Disallow .nu-env file in directory foo",
|
||||||
|
example: "autoenv untrust foo",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
use crate::commands::UnevaluatedCallInfo;
|
use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewConfiguration};
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
|
||||||
use crate::data::value::format_leaf;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::primitive::get_color_config;
|
||||||
|
use nu_data::value::format_leaf;
|
||||||
use nu_errors::ShellError;
|
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_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||||
|
use nu_table::TextStyle;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
|
|
||||||
use prettytable::{color, Attr, Cell, Row, Table};
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use textwrap::fill;
|
|
||||||
|
|
||||||
pub struct Autoview;
|
pub struct Command;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Autoview {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"autoview"
|
"autoview"
|
||||||
}
|
}
|
||||||
@@ -86,56 +84,32 @@ impl RunnableContextWithoutInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||||
|
let configuration = AutoViewConfiguration::new();
|
||||||
|
|
||||||
let binary = context.get_command("binaryview");
|
let binary = context.get_command("binaryview");
|
||||||
let text = context.get_command("textview");
|
let text = context.get_command("textview");
|
||||||
let table = context.get_command("table");
|
let table = context.get_command("table");
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
let pivot_mode = configuration.pivot_mode();
|
||||||
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 (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
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 {
|
if let Some(x) = input_stream.next().await {
|
||||||
match input_stream.next().await {
|
match input_stream.next().await {
|
||||||
Some(y) => {
|
Some(y) => {
|
||||||
let ctrl_c = context.ctrl_c.clone();
|
let ctrl_c = context.ctrl_c.clone();
|
||||||
let stream = async_stream! {
|
let xy = vec![x, y];
|
||||||
yield Ok(x);
|
let xy_stream = futures::stream::iter(xy)
|
||||||
yield Ok(y);
|
.chain(input_stream)
|
||||||
|
.interruptible(ctrl_c);
|
||||||
|
|
||||||
loop {
|
let stream = InputStream::from_stream(xy_stream);
|
||||||
match input_stream.next().await {
|
|
||||||
Some(z) => {
|
|
||||||
if ctrl_c.load(Ordering::SeqCst) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
yield Ok(z);
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let stream = stream.to_input_stream();
|
|
||||||
|
|
||||||
if let Some(table) = table {
|
if let Some(table) = table {
|
||||||
let command_args = create_default_command_args(&context).with_input(stream);
|
let command_args = create_default_command_args(&context).with_input(stream);
|
||||||
let result = table.run(command_args, &context.registry).await;
|
let result = table.run(command_args, &context.registry).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +126,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
);
|
);
|
||||||
let command_args =
|
let command_args =
|
||||||
create_default_command_args(&context).with_input(stream);
|
create_default_command_args(&context).with_input(stream);
|
||||||
let result = text.run(command_args, &context.registry).await;
|
let result = text.run(command_args, &context.registry).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{}", s);
|
out!("{}", s);
|
||||||
@@ -175,7 +149,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
);
|
);
|
||||||
let command_args =
|
let command_args =
|
||||||
create_default_command_args(&context).with_input(stream);
|
create_default_command_args(&context).with_input(stream);
|
||||||
let result = text.run(command_args, &context.registry).await;
|
let result = text.run(command_args, &context.registry).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{}\n", s);
|
out!("{}\n", s);
|
||||||
@@ -250,7 +224,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
stream.push_back(x);
|
stream.push_back(x);
|
||||||
let command_args =
|
let command_args =
|
||||||
create_default_command_args(&context).with_input(stream);
|
create_default_command_args(&context).with_input(stream);
|
||||||
let result = binary.run(command_args, &context.registry).await;
|
let result = binary.run(command_args, &context.registry).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
use pretty_hex::*;
|
use pretty_hex::*;
|
||||||
@@ -268,8 +242,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Row(row),
|
value: UntaggedValue::Row(row),
|
||||||
..
|
..
|
||||||
} if pivot_mode == AutoPivotMode::Always
|
} if pivot_mode.is_always()
|
||||||
|| (pivot_mode == AutoPivotMode::Auto
|
|| (pivot_mode.is_auto()
|
||||||
&& (row
|
&& (row
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
@@ -278,92 +252,29 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
.iter()
|
.iter()
|
||||||
.fold(0usize, |acc, len| acc + len.len())
|
.fold(0usize, |acc, len| acc + len.len())
|
||||||
+ row.entries.iter().count() * 2)
|
+ row.entries.iter().count() * 2)
|
||||||
> textwrap::termwidth()) =>
|
> term_width) =>
|
||||||
{
|
{
|
||||||
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
|
let mut entries = vec![];
|
||||||
|
|
||||||
enum TableMode {
|
|
||||||
Light,
|
|
||||||
Normal,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut table = Table::new();
|
|
||||||
let table_mode = crate::data::config::config(Tag::unknown());
|
|
||||||
|
|
||||||
let table_mode = if let Some(s) = table_mode?.get("table_mode") {
|
|
||||||
match s.as_string() {
|
|
||||||
Ok(typ) if typ == "light" => TableMode::Light,
|
|
||||||
_ => TableMode::Normal,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TableMode::Normal
|
|
||||||
};
|
|
||||||
|
|
||||||
match table_mode {
|
|
||||||
TableMode::Light => {
|
|
||||||
table.set_format(
|
|
||||||
FormatBuilder::new()
|
|
||||||
.separator(
|
|
||||||
LinePosition::Title,
|
|
||||||
LineSeparator::new('─', '─', ' ', ' '),
|
|
||||||
)
|
|
||||||
.separator(
|
|
||||||
LinePosition::Bottom,
|
|
||||||
LineSeparator::new(' ', ' ', ' ', ' '),
|
|
||||||
)
|
|
||||||
.padding(1, 1)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
table.set_format(
|
|
||||||
FormatBuilder::new()
|
|
||||||
.column_separator('│')
|
|
||||||
.separator(
|
|
||||||
LinePosition::Top,
|
|
||||||
LineSeparator::new('─', '┬', ' ', ' '),
|
|
||||||
)
|
|
||||||
.separator(
|
|
||||||
LinePosition::Title,
|
|
||||||
LineSeparator::new('─', '┼', ' ', ' '),
|
|
||||||
)
|
|
||||||
.separator(
|
|
||||||
LinePosition::Bottom,
|
|
||||||
LineSeparator::new('─', '┴', ' ', ' '),
|
|
||||||
)
|
|
||||||
.padding(1, 1)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut max_key_len = 0;
|
|
||||||
for (key, _) in row.entries.iter() {
|
|
||||||
max_key_len = std::cmp::max(max_key_len, key.chars().count());
|
|
||||||
}
|
|
||||||
|
|
||||||
if max_key_len > (termwidth / 2 - 1) {
|
|
||||||
max_key_len = termwidth / 2 - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_val_len = termwidth - max_key_len - 5;
|
|
||||||
|
|
||||||
for (key, value) in row.entries.iter() {
|
for (key, value) in row.entries.iter() {
|
||||||
table.add_row(Row::new(vec![
|
entries.push(vec![
|
||||||
Cell::new(&fill(&key, max_key_len))
|
nu_table::StyledString::new(
|
||||||
.with_style(Attr::ForegroundColor(color::GREEN))
|
key.to_string(),
|
||||||
.with_style(Attr::Bold),
|
TextStyle::new()
|
||||||
Cell::new(&fill(
|
.alignment(nu_table::Alignment::Left)
|
||||||
&format_leaf(value).plain_string(100_000),
|
.fg(ansi_term::Color::Green)
|
||||||
max_val_len,
|
.bold(Some(true)),
|
||||||
)),
|
),
|
||||||
]));
|
nu_table::StyledString::new(
|
||||||
|
format_leaf(value).plain_string(100_000),
|
||||||
|
nu_table::TextStyle::basic_left(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.printstd();
|
let table =
|
||||||
|
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||||
|
|
||||||
// table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
|
nu_table::draw_table(&table, term_width, &color_hm);
|
||||||
// .map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value {
|
Value {
|
||||||
@@ -374,7 +285,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
stream.push_back(x);
|
stream.push_back(x);
|
||||||
let command_args =
|
let command_args =
|
||||||
create_default_command_args(&context).with_input(stream);
|
create_default_command_args(&context).with_input(stream);
|
||||||
let result = table.run(command_args, &context.registry).await;
|
let result = table.run(command_args, &context.registry).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{:?}", item);
|
out!("{:?}", item);
|
||||||
@@ -404,22 +315,23 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
|||||||
positional: None,
|
positional: None,
|
||||||
named: None,
|
named: None,
|
||||||
span,
|
span,
|
||||||
is_last: true,
|
external_redirection: ExternalRedirection::Stdout,
|
||||||
},
|
},
|
||||||
name_tag: context.name.clone(),
|
name_tag: context.name.clone(),
|
||||||
scope: Scope::new(),
|
scope: Scope::create(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Autoview;
|
use super::Command;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Autoview {})
|
Ok(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;
|
||||||
51
crates/nu-cli/src/commands/autoview/options.rs
Normal file
51
crates/nu-cli/src/commands/autoview/options.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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 {
|
||||||
|
matches!(self, AutoPivotMode::Auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_always(&self) -> bool {
|
||||||
|
matches!(self, AutoPivotMode::Always)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn is_never(&self) -> bool {
|
||||||
|
matches!(self, AutoPivotMode::Never)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,173 +0,0 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
|
||||||
use bigdecimal::FromPrimitive;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::hir::{convert_number_to_u64, Number, Operator};
|
|
||||||
use nu_protocol::{
|
|
||||||
Dictionary, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use num_traits::identities::Zero;
|
|
||||||
|
|
||||||
use indexmap::map::IndexMap;
|
|
||||||
|
|
||||||
pub struct Average;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Average {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"average"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("average")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Average the values."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
average(RunnableContext {
|
|
||||||
input: args.input,
|
|
||||||
registry: registry.clone(),
|
|
||||||
shell_manager: args.shell_manager,
|
|
||||||
host: args.host,
|
|
||||||
ctrl_c: args.ctrl_c,
|
|
||||||
current_errors: args.current_errors,
|
|
||||||
name: args.call_info.name_tag,
|
|
||||||
raw_input: args.raw_input,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Average a list of numbers",
|
|
||||||
example: "echo [100 0 100 0] | average",
|
|
||||||
result: Some(vec![UntaggedValue::decimal(50).into()]),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn average(
|
|
||||||
RunnableContext {
|
|
||||||
mut input, name, ..
|
|
||||||
}: RunnableContext,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let stream = async_stream! {
|
|
||||||
let mut values: Vec<Value> = input.drain_vec().await;
|
|
||||||
let action = reducer_for(Reduce::Sum);
|
|
||||||
|
|
||||||
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
|
|
||||||
match avg(&values, name) {
|
|
||||||
Ok(result) => yield ReturnSuccess::value(result),
|
|
||||||
Err(err) => yield Err(err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut column_values = IndexMap::new();
|
|
||||||
for value in values {
|
|
||||||
match value.value {
|
|
||||||
UntaggedValue::Row(row_dict) => {
|
|
||||||
for (key, value) in row_dict.entries.iter() {
|
|
||||||
column_values
|
|
||||||
.entry(key.clone())
|
|
||||||
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
|
|
||||||
.or_insert(vec![value.clone()]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
table => {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut column_totals = IndexMap::new();
|
|
||||||
for (col_name, col_vals) in column_values {
|
|
||||||
match avg(&col_vals, &name) {
|
|
||||||
Ok(result) => {
|
|
||||||
column_totals.insert(col_name, result);
|
|
||||||
}
|
|
||||||
Err(err) => yield Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn avg(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
|
|
||||||
let name = name.into();
|
|
||||||
|
|
||||||
let sum = reducer_for(Reduce::Sum);
|
|
||||||
|
|
||||||
let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal");
|
|
||||||
|
|
||||||
let total_rows = UntaggedValue::decimal(number);
|
|
||||||
let total = sum(Value::zero(), values.to_vec())?;
|
|
||||||
|
|
||||||
match total {
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Primitive(Primitive::Bytes(num)),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
|
||||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
|
||||||
let number = Number::Decimal(result);
|
|
||||||
let number = convert_number_to_u64(&number);
|
|
||||||
Ok(UntaggedValue::bytes(number).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);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(value) => Ok(value.into_value(name)),
|
|
||||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
|
||||||
left_type.spanned(name.span),
|
|
||||||
right_type.spanned(name.span),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::labeled_error(
|
|
||||||
"could not calculate average of non-integer or unrelated types",
|
|
||||||
"source",
|
|
||||||
name,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Average;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Average {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
212
crates/nu-cli/src/commands/benchmark.rs
Normal file
212
crates/nu-cli/src/commands/benchmark.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
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, ClassifiedCommand, Commands, InternalCommand},
|
||||||
|
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use rand::{
|
||||||
|
distributions::Alphanumeric,
|
||||||
|
prelude::{thread_rng, Rng},
|
||||||
|
};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
pub struct Benchmark;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct BenchmarkArgs {
|
||||||
|
block: Block,
|
||||||
|
passthrough: Option<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Benchmark {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"benchmark"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
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 returns the time it took to execute it"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> 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(
|
||||||
|
raw_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
|
||||||
|
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, passthrough }, input) = raw_args.process(®istry).await?;
|
||||||
|
|
||||||
|
let env = scope.env();
|
||||||
|
let name = generate_free_name(&env);
|
||||||
|
let mut env = IndexMap::new();
|
||||||
|
env.insert(name, generate_random_env_value());
|
||||||
|
let scope = Scope::append_env(scope, env);
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
let start = time().await;
|
||||||
|
|
||||||
|
let result = run_block(&block, &mut context, input, scope.clone()).await;
|
||||||
|
let output = result?.into_vec().await;
|
||||||
|
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
let end = time().await;
|
||||||
|
|
||||||
|
let end_time = Instant::now();
|
||||||
|
context.clear_errors();
|
||||||
|
|
||||||
|
// 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: Arc<Scope>,
|
||||||
|
) -> Result<OutputStream, ShellError>
|
||||||
|
where
|
||||||
|
T: Into<Tag> + Copy,
|
||||||
|
Output: Into<OutputStream>,
|
||||||
|
{
|
||||||
|
let value = UntaggedValue::Row(Dictionary::from(
|
||||||
|
indexmap
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
|
||||||
|
.collect::<IndexMap<String, Value>>(),
|
||||||
|
))
|
||||||
|
.into_value(tag);
|
||||||
|
|
||||||
|
if let Some(time_block) = passthrough {
|
||||||
|
let benchmark_output = InputStream::one(value);
|
||||||
|
|
||||||
|
// add autoview for an empty block
|
||||||
|
let time_block = add_implicit_autoview(time_block);
|
||||||
|
|
||||||
|
let _ = run_block(&time_block, context, benchmark_output, scope).await?;
|
||||||
|
context.clear_errors();
|
||||||
|
|
||||||
|
Ok(block_output.into())
|
||||||
|
} else {
|
||||||
|
let benchmark_output = OutputStream::one(value);
|
||||||
|
Ok(benchmark_output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_implicit_autoview(mut block: Block) -> Block {
|
||||||
|
if block.block.is_empty() {
|
||||||
|
block.push({
|
||||||
|
let mut commands = Commands::new(block.span);
|
||||||
|
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
|
||||||
|
"autoview".to_string(),
|
||||||
|
block.span,
|
||||||
|
block.span,
|
||||||
|
)));
|
||||||
|
commands
|
||||||
|
});
|
||||||
|
}
|
||||||
|
block
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
|
||||||
|
time.try_into()
|
||||||
|
.unwrap_or_else(|_| Duration::new(0, 0))
|
||||||
|
.as_nanos()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_random_env_value() -> String {
|
||||||
|
let mut thread_rng = thread_rng();
|
||||||
|
let len = thread_rng.gen_range(1, 16 * 1024);
|
||||||
|
thread_rng.sample_iter(&Alphanumeric).take(len).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_free_name(env: &indexmap::IndexMap<String, String>) -> String {
|
||||||
|
let mut thread_rng = thread_rng();
|
||||||
|
loop {
|
||||||
|
let candidate_name = format!("NU_RANDOM_VALUE_{}", thread_rng.gen::<usize>());
|
||||||
|
if !env.contains_key(&candidate_name) {
|
||||||
|
return candidate_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use crate::prelude::*;
|
|||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::data::value::format_leaf;
|
use nu_data::value::format_leaf;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ impl WholeStreamCommand for Cal {
|
|||||||
"Display a year-long calendar for the specified year",
|
"Display a year-long calendar for the specified year",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"week-start",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Display the calendar with the specified day as the first day of the week",
|
||||||
|
None,
|
||||||
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"month-names",
|
"month-names",
|
||||||
"Display the month names instead of integers",
|
"Display the month names instead of integers",
|
||||||
@@ -55,6 +61,11 @@ impl WholeStreamCommand for Cal {
|
|||||||
example: "cal --full-year 2012",
|
example: "cal --full-year 2012",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "This month's calendar with the week starting on monday",
|
||||||
|
example: "cal --week-start monday",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,8 +84,7 @@ pub async fn cal(
|
|||||||
let mut selected_year: i32 = current_year;
|
let mut selected_year: i32 = current_year;
|
||||||
let mut current_day_option: Option<u32> = Some(current_day);
|
let mut current_day_option: Option<u32> = Some(current_day);
|
||||||
|
|
||||||
let month_range = if args.has("full-year") {
|
let month_range = if let Some(full_year_value) = args.get("full-year") {
|
||||||
if let Some(full_year_value) = args.get("full-year") {
|
|
||||||
if let Ok(year_u64) = full_year_value.as_u64() {
|
if let Ok(year_u64) = full_year_value.as_u64() {
|
||||||
selected_year = year_u64 as i32;
|
selected_year = year_u64 as i32;
|
||||||
|
|
||||||
@@ -84,14 +94,13 @@ pub async fn cal(
|
|||||||
} else {
|
} else {
|
||||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
|
return Err(get_invalid_year_shell_error(&full_year_value.tag()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(1, 12)
|
(1, 12)
|
||||||
} else {
|
} else {
|
||||||
(current_month, current_month)
|
(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,
|
&args,
|
||||||
&mut calendar_vec_deque,
|
&mut calendar_vec_deque,
|
||||||
&tag,
|
&tag,
|
||||||
@@ -99,12 +108,9 @@ pub async fn cal(
|
|||||||
month_range,
|
month_range,
|
||||||
current_month,
|
current_month,
|
||||||
current_day_option,
|
current_day_option,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
match add_months_of_year_to_table_result {
|
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
|
||||||
Ok(()) => Ok(futures::stream::iter(calendar_vec_deque).to_output_stream()),
|
|
||||||
Err(error) => Err(error),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
||||||
@@ -112,90 +118,48 @@ fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct MonthHelper {
|
struct MonthHelper {
|
||||||
day_number_month_starts_on: u32,
|
|
||||||
number_of_days_in_month: u32,
|
|
||||||
selected_year: i32,
|
selected_year: i32,
|
||||||
selected_month: u32,
|
selected_month: u32,
|
||||||
|
day_number_of_week_month_starts_on: u32,
|
||||||
|
number_of_days_in_month: u32,
|
||||||
|
quarter_number: u32,
|
||||||
|
month_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonthHelper {
|
impl MonthHelper {
|
||||||
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
|
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
|
||||||
let mut month_helper = MonthHelper {
|
let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||||
day_number_month_starts_on: 0,
|
let number_of_days_in_month =
|
||||||
number_of_days_in_month: 0,
|
MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
|
||||||
|
|
||||||
|
Ok(MonthHelper {
|
||||||
selected_year,
|
selected_year,
|
||||||
selected_month,
|
selected_month,
|
||||||
};
|
day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
|
||||||
|
number_of_days_in_month,
|
||||||
let chosen_date_result_one = month_helper.update_day_number_month_starts_on();
|
quarter_number: ((selected_month - 1) / 3) + 1,
|
||||||
let chosen_date_result_two = month_helper.update_number_of_days_in_month();
|
month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
|
||||||
|
})
|
||||||
if chosen_date_result_one.is_ok() && chosen_date_result_two.is_ok() {
|
|
||||||
return Ok(month_helper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(())
|
fn calculate_number_of_days_in_month(
|
||||||
}
|
mut selected_year: i32,
|
||||||
|
mut selected_month: u32,
|
||||||
pub fn get_month_name(&self) -> String {
|
) -> Result<u32, ()> {
|
||||||
let month_name = match self.selected_month {
|
|
||||||
1 => "january",
|
|
||||||
2 => "february",
|
|
||||||
3 => "march",
|
|
||||||
4 => "april",
|
|
||||||
5 => "may",
|
|
||||||
6 => "june",
|
|
||||||
7 => "july",
|
|
||||||
8 => "august",
|
|
||||||
9 => "september",
|
|
||||||
10 => "october",
|
|
||||||
11 => "november",
|
|
||||||
_ => "december",
|
|
||||||
};
|
|
||||||
|
|
||||||
month_name.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_day_number_month_starts_on(&mut self) -> Result<(), ()> {
|
|
||||||
let naive_date_result =
|
|
||||||
MonthHelper::get_naive_date(self.selected_year, self.selected_month);
|
|
||||||
|
|
||||||
match naive_date_result {
|
|
||||||
Ok(naive_date) => {
|
|
||||||
self.day_number_month_starts_on = naive_date.weekday().num_days_from_sunday();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_number_of_days_in_month(&mut self) -> Result<(), ()> {
|
|
||||||
// Chrono does not provide a method to output the amount of days in a month
|
// Chrono does not provide a method to output the amount of days in a month
|
||||||
// This is a workaround taken from the example code from the Chrono docs here:
|
// This is a workaround taken from the example code from the Chrono docs here:
|
||||||
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
|
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
|
||||||
let (adjusted_year, adjusted_month) = if self.selected_month == 12 {
|
if selected_month == 12 {
|
||||||
(self.selected_year + 1, 1)
|
selected_year += 1;
|
||||||
|
selected_month = 1;
|
||||||
} else {
|
} else {
|
||||||
(self.selected_year, self.selected_month + 1)
|
selected_month += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
let naive_date_result = MonthHelper::get_naive_date(adjusted_year, adjusted_month);
|
let next_month_naive_date =
|
||||||
|
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||||
|
|
||||||
match naive_date_result {
|
Ok(next_month_naive_date.pred().day())
|
||||||
Ok(naive_date) => {
|
|
||||||
self.number_of_days_in_month = naive_date.pred().day();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_naive_date(selected_year: i32, selected_month: u32) -> Result<NaiveDate, ()> {
|
|
||||||
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
|
|
||||||
return Ok(naive_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,10 +232,7 @@ fn add_month_to_table(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let day_limit = month_helper.number_of_days_in_month + month_helper.day_number_month_starts_on;
|
let mut days_of_the_week = [
|
||||||
let mut day_count: u32 = 1;
|
|
||||||
|
|
||||||
let days_of_the_week = [
|
|
||||||
"sunday",
|
"sunday",
|
||||||
"monday",
|
"monday",
|
||||||
"tuesday",
|
"tuesday",
|
||||||
@@ -281,12 +242,43 @@ fn add_month_to_table(
|
|||||||
"saturday",
|
"saturday",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mut week_start_day = days_of_the_week[0].to_string();
|
||||||
|
|
||||||
|
if let Some(week_start_value) = args.get("week-start") {
|
||||||
|
if let Ok(day) = week_start_value.as_string() {
|
||||||
|
if days_of_the_week.contains(&day.as_str()) {
|
||||||
|
week_start_day = day;
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"The specified week start day is invalid",
|
||||||
|
"invalid week start day",
|
||||||
|
week_start_value.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let week_start_day_offset = days_of_the_week.len()
|
||||||
|
- days_of_the_week
|
||||||
|
.iter()
|
||||||
|
.position(|day| *day == week_start_day)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
days_of_the_week.rotate_right(week_start_day_offset);
|
||||||
|
|
||||||
|
let mut total_start_offset: u32 =
|
||||||
|
month_helper.day_number_of_week_month_starts_on + week_start_day_offset as u32;
|
||||||
|
total_start_offset %= days_of_the_week.len() as u32;
|
||||||
|
|
||||||
|
let mut day_number: u32 = 1;
|
||||||
|
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
|
||||||
|
|
||||||
let should_show_year_column = args.has("year");
|
let should_show_year_column = args.has("year");
|
||||||
let should_show_month_column = args.has("month");
|
|
||||||
let should_show_quarter_column = args.has("quarter");
|
let should_show_quarter_column = args.has("quarter");
|
||||||
|
let should_show_month_column = args.has("month");
|
||||||
let should_show_month_names = args.has("month-names");
|
let should_show_month_names = args.has("month-names");
|
||||||
|
|
||||||
while day_count <= day_limit {
|
while day_number <= day_limit {
|
||||||
let mut indexmap = IndexMap::new();
|
let mut indexmap = IndexMap::new();
|
||||||
|
|
||||||
if should_show_year_column {
|
if should_show_year_column {
|
||||||
@@ -299,13 +291,13 @@ fn add_month_to_table(
|
|||||||
if should_show_quarter_column {
|
if should_show_quarter_column {
|
||||||
indexmap.insert(
|
indexmap.insert(
|
||||||
"quarter".to_string(),
|
"quarter".to_string(),
|
||||||
UntaggedValue::int(((month_helper.selected_month - 1) / 3) + 1).into_value(tag),
|
UntaggedValue::int(month_helper.quarter_number).into_value(tag),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_show_month_column {
|
if should_show_month_column || should_show_month_names {
|
||||||
let month_value = if should_show_month_names {
|
let month_value = if should_show_month_names {
|
||||||
UntaggedValue::string(month_helper.get_month_name()).into_value(tag)
|
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
|
||||||
} else {
|
} else {
|
||||||
UntaggedValue::int(month_helper.selected_month).into_value(tag)
|
UntaggedValue::int(month_helper.selected_month).into_value(tag)
|
||||||
};
|
};
|
||||||
@@ -315,17 +307,17 @@ fn add_month_to_table(
|
|||||||
|
|
||||||
for day in &days_of_the_week {
|
for day in &days_of_the_week {
|
||||||
let should_add_day_number_to_table =
|
let should_add_day_number_to_table =
|
||||||
(day_count <= day_limit) && (day_count > month_helper.day_number_month_starts_on);
|
(day_number > total_start_offset) && (day_number <= day_limit);
|
||||||
|
|
||||||
let mut value = UntaggedValue::nothing().into_value(tag);
|
let mut value = UntaggedValue::nothing().into_value(tag);
|
||||||
|
|
||||||
if should_add_day_number_to_table {
|
if should_add_day_number_to_table {
|
||||||
let day_count_with_offset = day_count - month_helper.day_number_month_starts_on;
|
let adjusted_day_number = day_number - total_start_offset;
|
||||||
|
|
||||||
value = UntaggedValue::int(day_count_with_offset).into_value(tag);
|
value = UntaggedValue::int(adjusted_day_number).into_value(tag);
|
||||||
|
|
||||||
if let Some(current_day) = current_day_option {
|
if let Some(current_day) = current_day_option {
|
||||||
if current_day == day_count_with_offset {
|
if current_day == adjusted_day_number {
|
||||||
// TODO: Update the value here with a color when color support is added
|
// TODO: Update the value here with a color when color support is added
|
||||||
// This colors the current day
|
// This colors the current day
|
||||||
}
|
}
|
||||||
@@ -334,7 +326,7 @@ fn add_month_to_table(
|
|||||||
|
|
||||||
indexmap.insert((*day).to_string(), value);
|
indexmap.insert((*day).to_string(), value);
|
||||||
|
|
||||||
day_count += 1;
|
day_number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
calendar_vec_deque
|
calendar_vec_deque
|
||||||
@@ -347,11 +339,12 @@ fn add_month_to_table(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cal;
|
use super::Cal;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Cal {})
|
Ok(test_examples(Cal {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
|
||||||
|
|
||||||
pub struct Calc;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Calc {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"calc"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Parse a math expression into a number"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
calc(args, registry).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Calculate math in the pipeline",
|
|
||||||
example: "echo '10 / 4' | calc",
|
|
||||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn calc(
|
|
||||||
args: CommandArgs,
|
|
||||||
_registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let input = args.input;
|
|
||||||
let name = args.call_info.name_tag.span;
|
|
||||||
|
|
||||||
Ok(input
|
|
||||||
.map(move |input| {
|
|
||||||
if let Ok(string) = input.as_string() {
|
|
||||||
match parse(&string, &input.tag) {
|
|
||||||
Ok(value) => ReturnSuccess::value(value),
|
|
||||||
Err(err) => Err(ShellError::labeled_error(
|
|
||||||
"Calculation error",
|
|
||||||
err,
|
|
||||||
&input.tag.span,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"Expected a string from pipeline",
|
|
||||||
"requires string input",
|
|
||||||
name,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {
|
|
||||||
use std::f64;
|
|
||||||
let num = meval::eval_str(math_expression);
|
|
||||||
match num {
|
|
||||||
Ok(num) => {
|
|
||||||
if num == f64::INFINITY || num == f64::NEG_INFINITY {
|
|
||||||
return Err(String::from("cannot represent result"));
|
|
||||||
}
|
|
||||||
Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag))
|
|
||||||
}
|
|
||||||
Err(error) => Err(error.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Calc;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Calc {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -72,11 +72,12 @@ impl WholeStreamCommand for Cd {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cd;
|
use super::Cd;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Cd {})
|
Ok(test_examples(Cd {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
141
crates/nu-cli/src/commands/char_.rs
Normal file
141
crates/nu-cli/src/commands/char_.rs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Char;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CharArgs {
|
||||||
|
name: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Char {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"char"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ansi").required(
|
||||||
|
"character",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the name of the character to output",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Output special characters (eg. 'newline')"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Output newline",
|
||||||
|
example: r#"char newline"#,
|
||||||
|
result: Some(vec![Value::from("\n")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Output prompt character, newline and a hamburger character",
|
||||||
|
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("\u{25b6}").into(),
|
||||||
|
UntaggedValue::string("\n").into(),
|
||||||
|
UntaggedValue::string("\u{2261}").into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let special_character = str_to_character(&name.item);
|
||||||
|
|
||||||
|
if let Some(output) = special_character {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Unknown character",
|
||||||
|
"unknown character",
|
||||||
|
name.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_to_character(s: &str) -> Option<String> {
|
||||||
|
match s {
|
||||||
|
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||||
|
"tab" => Some("\t".into()),
|
||||||
|
"sp" | "space" => Some(" ".into()),
|
||||||
|
// Unicode names came from https://www.compart.com/en/unicode
|
||||||
|
// Private Use Area (U+E000-U+F8FF)
|
||||||
|
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||||
|
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||||
|
|
||||||
|
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
|
||||||
|
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
|
||||||
|
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
|
||||||
|
|
||||||
|
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
|
||||||
|
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
|
||||||
|
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
|
||||||
|
|
||||||
|
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
|
||||||
|
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Char;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Char {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
53
crates/nu-cli/src/commands/chart.rs
Normal file
53
crates/nu-cli/src/commands/chart.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Chart;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Chart {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"chart"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("chart")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Displays charts."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
if registry.get_command("chart bar").is_none() {
|
||||||
|
return Err(ShellError::untagged_runtime_error(
|
||||||
|
"nu_plugin_chart not installed.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let registry = registry.clone();
|
||||||
|
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Chart, ®istry))
|
||||||
|
.into_value(Tag::unknown()),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Chart;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Chart {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
use crate::commands::classified::expr::run_expression_block;
|
use crate::commands::classified::expr::run_expression_block;
|
||||||
use crate::commands::classified::internal::run_internal_command;
|
use crate::commands::classified::internal::run_internal_command;
|
||||||
use crate::context::Context;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::stream::InputStream;
|
use crate::stream::InputStream;
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(crate) async fn run_block(
|
pub(crate) async fn run_block(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||||
for pipeline in &block.block {
|
for pipeline in &block.block {
|
||||||
@@ -54,7 +52,7 @@ pub(crate) async fn run_block(
|
|||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
output = run_pipeline(pipeline, ctx, input, scope.clone()).await;
|
||||||
|
|
||||||
input = InputStream::empty();
|
input = InputStream::empty();
|
||||||
}
|
}
|
||||||
@@ -64,33 +62,25 @@ pub(crate) async fn run_block(
|
|||||||
|
|
||||||
async fn run_pipeline(
|
async fn run_pipeline(
|
||||||
commands: &Commands,
|
commands: &Commands,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let mut iter = commands.list.clone().into_iter().peekable();
|
for item in commands.list.clone() {
|
||||||
loop {
|
input = match item {
|
||||||
let item: Option<ClassifiedCommand> = iter.next();
|
ClassifiedCommand::Dynamic(_) => {
|
||||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
|
||||||
|
|
||||||
input = match (item, next) {
|
|
||||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
|
||||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
ClassifiedCommand::Expr(expr) => {
|
||||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
run_expression_block(*expr, ctx, scope.clone()).await?
|
||||||
}
|
|
||||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
|
||||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
|
||||||
|
|
||||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
|
||||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, _) => break,
|
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||||
|
|
||||||
|
ClassifiedCommand::Internal(left) => {
|
||||||
|
run_internal_command(left, ctx, input, scope.clone()).await?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ use log::{log_enabled, trace};
|
|||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::SpannedExpression;
|
use nu_protocol::hir::SpannedExpression;
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Scope;
|
||||||
|
|
||||||
pub(crate) async fn run_expression_block(
|
pub(crate) async fn run_expression_block(
|
||||||
expr: SpannedExpression,
|
expr: SpannedExpression,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
if log_enabled!(log::Level::Trace) {
|
if log_enabled!(log::Level::Trace) {
|
||||||
trace!(target: "nu::run::expr", "->");
|
trace!(target: "nu::run::expr", "->");
|
||||||
@@ -21,7 +19,7 @@ pub(crate) async fn run_expression_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let registry = context.registry().clone();
|
let registry = context.registry().clone();
|
||||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
let output = evaluate_baseline_expr(&expr, ®istry, scope).await?;
|
||||||
|
|
||||||
Ok(once(async { Ok(output) }).to_input_stream())
|
Ok(once(async { Ok(output) }).to_input_stream())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::futures::ThreadedReceiver;
|
use crate::futures::ThreadedReceiver;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -7,91 +8,25 @@ use std::ops::Deref;
|
|||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
|
||||||
use futures::executor::block_on_stream;
|
use futures::executor::block_on_stream;
|
||||||
// use futures::stream::StreamExt;
|
|
||||||
use futures_codec::FramedRead;
|
use futures_codec::FramedRead;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
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_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
pub enum StringOrBinary {
|
|
||||||
String(String),
|
|
||||||
Binary(Vec<u8>),
|
|
||||||
}
|
|
||||||
pub struct MaybeTextCodec;
|
|
||||||
|
|
||||||
impl futures_codec::Encoder for MaybeTextCodec {
|
|
||||||
type Item = StringOrBinary;
|
|
||||||
type Error = std::io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
match item {
|
|
||||||
StringOrBinary::String(s) => {
|
|
||||||
dst.reserve(s.len());
|
|
||||||
dst.put(s.as_bytes());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
StringOrBinary::Binary(b) => {
|
|
||||||
dst.reserve(b.len());
|
|
||||||
dst.put(Bytes::from(b));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl futures_codec::Decoder for MaybeTextCodec {
|
|
||||||
type Item = StringOrBinary;
|
|
||||||
type Error = std::io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
let v: Vec<u8> = src.to_vec();
|
|
||||||
match String::from_utf8(v) {
|
|
||||||
Ok(s) => {
|
|
||||||
src.clear();
|
|
||||||
if s.is_empty() {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some(StringOrBinary::String(s)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// Note: the longest UTF-8 character per Unicode spec is currently 6 bytes. If we fail somewhere earlier than the last 6 bytes,
|
|
||||||
// we know that we're failing to understand the string encoding and not just seeing a partial character. When this happens, let's
|
|
||||||
// fall back to assuming it's a binary buffer.
|
|
||||||
if src.is_empty() {
|
|
||||||
Ok(None)
|
|
||||||
} else if src.len() > 6 && (src.len() - err.utf8_error().valid_up_to() > 6) {
|
|
||||||
// Fall back to assuming binary
|
|
||||||
let buf = src.to_vec();
|
|
||||||
src.clear();
|
|
||||||
Ok(Some(StringOrBinary::Binary(buf)))
|
|
||||||
} else {
|
|
||||||
// Looks like a utf-8 string, so let's assume that
|
|
||||||
let buf = src.split_to(err.utf8_error().valid_up_to() + 1);
|
|
||||||
String::from_utf8(buf.to_vec())
|
|
||||||
.map(|x| Some(StringOrBinary::String(x)))
|
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn run_external_command(
|
pub(crate) async fn run_external_command(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
is_last: bool,
|
external_redirection: ExternalRedirection,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||||
|
|
||||||
if !did_find_command(&command.name).await {
|
if !did_find_command(&command.name) {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Command not found",
|
"Command not found",
|
||||||
"command not found",
|
"command not found",
|
||||||
@@ -99,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(
|
async fn run_with_stdin(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
is_last: bool,
|
external_redirection: ExternalRedirection,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let path = context.shell_manager.path();
|
let path = context.shell_manager.path();
|
||||||
|
|
||||||
@@ -115,38 +50,56 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
let mut command_args = vec![];
|
let mut command_args = vec![];
|
||||||
for arg in command.args.iter() {
|
for arg in command.args.iter() {
|
||||||
let value =
|
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
|
||||||
.await?;
|
|
||||||
// Skip any arguments that don't really exist, treating them as optional
|
// Skip any arguments that don't really exist, treating them as optional
|
||||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||||
// what value we would put in its place.
|
// what value we would put in its place.
|
||||||
if value.value.is_none() {
|
if value.value.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the cleanup that we need to do on any argument going out:
|
// Do the cleanup that we need to do on any argument going out:
|
||||||
|
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();
|
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||||
|
command_args.push(trimmed_value_string);
|
||||||
let value_string;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
{
|
|
||||||
value_string = trimmed_value_string
|
|
||||||
.replace('$', "\\$")
|
|
||||||
.replace('"', "\\\"")
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
value_string = trimmed_value_string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command_args.push(value_string);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let process_args = command_args
|
let process_args = command_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|arg| {
|
||||||
let arg = expand_tilde(arg.deref(), dirs::home_dir);
|
let home_dir;
|
||||||
|
|
||||||
|
#[cfg(feature = "dirs")]
|
||||||
|
{
|
||||||
|
home_dir = dirs::home_dir;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "dirs"))]
|
||||||
|
{
|
||||||
|
home_dir = || Some(std::path::PathBuf::from("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = expand_tilde(arg.deref(), home_dir);
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
@@ -167,7 +120,14 @@ async fn run_with_stdin(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
spawn(&command, &path, &process_args[..], input, is_last, scope)
|
spawn(
|
||||||
|
&command,
|
||||||
|
&path,
|
||||||
|
&process_args[..],
|
||||||
|
input,
|
||||||
|
external_redirection,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
@@ -175,8 +135,8 @@ fn spawn(
|
|||||||
path: &str,
|
path: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
is_last: bool,
|
external_redirection: ExternalRedirection,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
|
|
||||||
@@ -207,14 +167,27 @@ fn spawn(
|
|||||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||||
|
|
||||||
process.env_clear();
|
process.env_clear();
|
||||||
process.envs(scope.env.iter());
|
process.envs(scope.env());
|
||||||
|
|
||||||
// We want stdout regardless of what
|
// We want stdout regardless of what
|
||||||
// we are doing ($it case or pipe stdin)
|
// we are doing ($it case or pipe stdin)
|
||||||
if !is_last {
|
match external_redirection {
|
||||||
|
ExternalRedirection::Stdout => {
|
||||||
process.stdout(Stdio::piped());
|
process.stdout(Stdio::piped());
|
||||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
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
|
// open since we have some contents for stdin
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
@@ -297,7 +270,9 @@ fn spawn(
|
|||||||
});
|
});
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
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() {
|
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||||
stdout
|
stdout
|
||||||
} else {
|
} else {
|
||||||
@@ -313,7 +288,7 @@ fn spawn(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let file = futures::io::AllowStdIo::new(stdout);
|
let file = futures::io::AllowStdIo::new(stdout);
|
||||||
let stream = FramedRead::new(file, MaybeTextCodec);
|
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||||
|
|
||||||
for line in block_on_stream(stream) {
|
for line in block_on_stream(stream) {
|
||||||
match line {
|
match line {
|
||||||
@@ -366,6 +341,79 @@ 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 stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringOrBinary::Binary(_) => {
|
||||||
|
let result = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(
|
||||||
|
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||||
|
),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// If there's an exit status, it makes sense that we may error when
|
||||||
|
// trying to read from its stdout pipe (likely been closed). In that
|
||||||
|
// case, don't emit an error.
|
||||||
|
let should_error = match child.wait() {
|
||||||
|
Ok(exit_status) => !exit_status.success(),
|
||||||
|
Err(_) => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_error {
|
||||||
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
|
format!("Unable to read from stdout ({})", e),
|
||||||
|
"unable to read from stdout",
|
||||||
|
&stdout_name_tag,
|
||||||
|
)),
|
||||||
|
tag: stdout_name_tag.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We can give an error when we see a non-zero exit code, but this is different
|
// We can give an error when we see a non-zero exit code, but this is different
|
||||||
// than what other shells will do.
|
// than what other shells will do.
|
||||||
@@ -375,7 +423,7 @@ fn spawn(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if external_failed {
|
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 let Ok(cfg) = cfg {
|
||||||
if cfg.contains_key("nonzero_exit_errors") {
|
if cfg.contains_key("nonzero_exit_errors") {
|
||||||
let _ = stdout_read_tx.send(Ok(Value {
|
let _ = stdout_read_tx.send(Ok(Value {
|
||||||
@@ -408,21 +456,28 @@ fn spawn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn did_find_command(name: &str) -> bool {
|
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(feature = "which"))]
|
||||||
|
{
|
||||||
|
// we can't perform this check, so just assume it can be found
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "which", unix))]
|
||||||
{
|
{
|
||||||
which::which(name).is_ok()
|
which::which(name).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(all(feature = "which", windows))]
|
||||||
{
|
{
|
||||||
if which::which(name).is_ok() {
|
if which::which(name).is_ok() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
// Reference: https://ss64.com/nt/syntax-internal.html
|
||||||
let cmd_builtins = [
|
let cmd_builtins = [
|
||||||
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
|
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
|
||||||
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
|
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
|
||||||
"mklink",
|
"rmdir", "set", "start", "time", "title", "type", "ver", "verify", "vol",
|
||||||
];
|
];
|
||||||
|
|
||||||
cmd_builtins.contains(&name)
|
cmd_builtins.contains(&name)
|
||||||
@@ -484,11 +539,17 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||||
run_external_command, Context, InputStream,
|
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
use super::{run_external_command, EvaluationContext, InputStream};
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
#[cfg(feature = "which")]
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
#[cfg(feature = "which")]
|
||||||
use nu_protocol::Scope;
|
use nu_protocol::Scope;
|
||||||
|
#[cfg(feature = "which")]
|
||||||
use nu_test_support::commands::ExternalBuilder;
|
use nu_test_support::commands::ExternalBuilder;
|
||||||
|
|
||||||
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
||||||
@@ -504,17 +565,24 @@ mod tests {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
async fn non_existent_run() -> Result<(), ShellError> {
|
async fn non_existent_run() -> Result<(), ShellError> {
|
||||||
|
use nu_protocol::hir::ExternalRedirection;
|
||||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||||
|
|
||||||
let input = InputStream::empty();
|
let input = InputStream::empty();
|
||||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
let mut ctx =
|
||||||
|
EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||||
|
|
||||||
assert!(
|
assert!(run_external_command(
|
||||||
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
|
cmd,
|
||||||
|
&mut ctx,
|
||||||
|
input,
|
||||||
|
Scope::create(),
|
||||||
|
ExternalRedirection::Stdout
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err());
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -522,7 +590,7 @@ mod tests {
|
|||||||
// async fn failure_run() -> Result<(), ShellError> {
|
// async fn failure_run() -> Result<(), ShellError> {
|
||||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||||
|
|
||||||
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
// let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||||
// .await?
|
// .await?
|
||||||
// .expect("There was a problem running the external command.");
|
// .expect("There was a problem running the external command.");
|
||||||
@@ -543,6 +611,7 @@ mod tests {
|
|||||||
// block_on(failure_run())
|
// block_on(failure_run())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
#[test]
|
#[test]
|
||||||
fn identifies_command_not_found() -> Result<(), ShellError> {
|
fn identifies_command_not_found() -> Result<(), ShellError> {
|
||||||
block_on(non_existent_run())
|
block_on(non_existent_run())
|
||||||
|
|||||||
@@ -4,65 +4,69 @@ use crate::commands::UnevaluatedCallInfo;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use log::{log_enabled, trace};
|
use log::{log_enabled, trace};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::InternalCommand;
|
use nu_protocol::hir::{ExternalRedirection, InternalCommand};
|
||||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||||
|
|
||||||
pub(crate) async fn run_internal_command(
|
pub(crate) async fn run_internal_command(
|
||||||
command: InternalCommand,
|
command: InternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
it: &Value,
|
scope: Arc<Scope>,
|
||||||
vars: &IndexMap<String, Value>,
|
|
||||||
env: &IndexMap<String, String>,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
if log_enabled!(log::Level::Trace) {
|
if log_enabled!(log::Level::Trace) {
|
||||||
trace!(target: "nu::run::internal", "->");
|
trace!(target: "nu::run::internal", "->");
|
||||||
trace!(target: "nu::run::internal", "{}", command.name);
|
trace!(target: "nu::run::internal", "{}", command.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = Scope {
|
|
||||||
it: it.clone(),
|
|
||||||
vars: vars.clone(),
|
|
||||||
env: env.clone(),
|
|
||||||
};
|
|
||||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||||
let internal_command = context.expect_command(&command.name);
|
let internal_command = context.expect_command(&command.name);
|
||||||
|
|
||||||
let mut result = {
|
if command.name == "autoenv untrust" {
|
||||||
|
context.user_recently_used_autoenv_untrust = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = {
|
||||||
context
|
context
|
||||||
.run_command(
|
.run_command(
|
||||||
internal_command?,
|
internal_command?,
|
||||||
Tag::unknown_anchor(command.name_span),
|
Tag::unknown_anchor(command.name_span),
|
||||||
command.args.clone(),
|
command.args.clone(),
|
||||||
&scope,
|
scope.clone(),
|
||||||
objects,
|
objects,
|
||||||
)
|
)
|
||||||
.await
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let head = Arc::new(command.args.head.clone());
|
||||||
|
//let context = Arc::new(context.clone());
|
||||||
|
let context = context.clone();
|
||||||
|
let command = Arc::new(command);
|
||||||
|
|
||||||
|
Ok(InputStream::from_stream(
|
||||||
|
result
|
||||||
|
.then(move |item| {
|
||||||
|
let head = head.clone();
|
||||||
|
let command = command.clone();
|
||||||
let mut context = context.clone();
|
let mut context = context.clone();
|
||||||
// let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
|
async move {
|
||||||
let stream = async_stream! {
|
|
||||||
let mut soft_errs: Vec<ShellError> = vec![];
|
|
||||||
let mut yielded = false;
|
|
||||||
|
|
||||||
while let Some(item) = result.next().await {
|
|
||||||
match item {
|
match item {
|
||||||
Ok(ReturnSuccess::Action(action)) => match action {
|
Ok(ReturnSuccess::Action(action)) => match action {
|
||||||
CommandAction::ChangePath(path) => {
|
CommandAction::ChangePath(path) => {
|
||||||
context.shell_manager.set_path(path);
|
context.shell_manager.set_path(path);
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||||
CommandAction::Error(err) => {
|
CommandAction::Error(err) => {
|
||||||
context.error(err);
|
context.error(err.clone());
|
||||||
break;
|
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||||
}
|
}
|
||||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||||
let contents_tag = tagged_contents.tag.clone();
|
let contents_tag = tagged_contents.tag.clone();
|
||||||
let command_name = format!("from {}", extension);
|
let command_name = format!("from {}", extension);
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
if let Some(converter) = context.registry.get_command(&command_name) {
|
if let Some(converter) = context.registry.get_command(&command_name)
|
||||||
|
{
|
||||||
let new_args = RawCommandArgs {
|
let new_args = RawCommandArgs {
|
||||||
host: context.host.clone(),
|
host: context.host.clone(),
|
||||||
ctrl_c: context.ctrl_c.clone(),
|
ctrl_c: context.ctrl_c.clone(),
|
||||||
@@ -70,106 +74,173 @@ pub(crate) async fn run_internal_command(
|
|||||||
shell_manager: context.shell_manager.clone(),
|
shell_manager: context.shell_manager.clone(),
|
||||||
call_info: UnevaluatedCallInfo {
|
call_info: UnevaluatedCallInfo {
|
||||||
args: nu_protocol::hir::Call {
|
args: nu_protocol::hir::Call {
|
||||||
head: command.args.head,
|
head: (&*head).clone(),
|
||||||
positional: None,
|
positional: None,
|
||||||
named: None,
|
named: None,
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
is_last: false,
|
external_redirection: ExternalRedirection::Stdout,
|
||||||
},
|
},
|
||||||
name_tag: Tag::unknown_anchor(command.name_span),
|
name_tag: Tag::unknown_anchor(command.name_span),
|
||||||
scope: scope.clone(),
|
scope,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry).await;
|
let result = converter
|
||||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
|
.run(
|
||||||
|
new_args.with_input(vec![tagged_contents]),
|
||||||
|
&context.registry,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(mut result) => {
|
||||||
|
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
|
result.drain_vec().await;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
for res in result_vec {
|
for res in result_vec {
|
||||||
match res {
|
match res {
|
||||||
Ok(ReturnSuccess::Value(Value { value: UntaggedValue::Table(list), ..})) => {
|
Ok(ReturnSuccess::Value(Value {
|
||||||
|
value: UntaggedValue::Table(list),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
for l in list {
|
for l in list {
|
||||||
yield Ok(l);
|
output.push(Ok(l));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ReturnSuccess::Value(Value { value, .. })) => {
|
Ok(ReturnSuccess::Value(Value {
|
||||||
yield Ok(value.into_value(contents_tag.clone()));
|
value,
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
output
|
||||||
|
.push(Ok(value
|
||||||
|
.into_value(contents_tag.clone())));
|
||||||
}
|
}
|
||||||
Err(e) => yield Err(e),
|
Err(e) => output.push(Err(e)),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
futures::stream::iter(output).to_input_stream()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
context.add_error(e);
|
||||||
|
InputStream::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
yield Ok(tagged_contents)
|
InputStream::one(tagged_contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandAction::EnterHelpShell(value) => {
|
CommandAction::EnterHelpShell(value) => match value {
|
||||||
match value {
|
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||||
tag,
|
tag,
|
||||||
} => {
|
} => {
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
HelpShell::for_command(
|
match HelpShell::for_command(
|
||||||
UntaggedValue::string(cmd).into_value(tag),
|
UntaggedValue::string(cmd).into_value(tag),
|
||||||
&context.registry(),
|
&context.registry(),
|
||||||
)?,
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
return InputStream::one(
|
||||||
|
UntaggedValue::Error(err).into_untagged_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
));
|
));
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
HelpShell::index(&context.registry())?,
|
match HelpShell::index(&context.registry()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
return InputStream::one(
|
||||||
|
UntaggedValue::Error(err).into_untagged_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
));
|
));
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
CommandAction::EnterValueShell(value) => {
|
CommandAction::EnterValueShell(value) => {
|
||||||
context
|
context
|
||||||
.shell_manager
|
.shell_manager
|
||||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::EnterShell(location) => {
|
CommandAction::EnterShell(location) => {
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
FilesystemShell::with_location(location, context.registry().clone())?,
|
match FilesystemShell::with_location(location) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
return InputStream::one(
|
||||||
|
UntaggedValue::Error(err.into())
|
||||||
|
.into_untagged_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
));
|
));
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::AddAlias(name, args, block) => {
|
CommandAction::AddAlias(name, args, block) => {
|
||||||
context.add_commands(vec![
|
context.add_commands(vec![whole_stream_command(
|
||||||
whole_stream_command(AliasCommand::new(
|
AliasCommand::new(name, args, block),
|
||||||
name,
|
)]);
|
||||||
args,
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
block,
|
}
|
||||||
))
|
CommandAction::AddPlugins(path) => {
|
||||||
]);
|
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||||
|
Ok(plugins) => {
|
||||||
|
context.add_commands(
|
||||||
|
plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| {
|
||||||
|
!context.is_command_registered(p.name())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
context.error(reason.clone());
|
||||||
|
InputStream::one(
|
||||||
|
UntaggedValue::Error(reason).into_untagged_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CommandAction::PreviousShell => {
|
CommandAction::PreviousShell => {
|
||||||
context.shell_manager.prev();
|
context.shell_manager.prev();
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::NextShell => {
|
CommandAction::NextShell => {
|
||||||
context.shell_manager.next();
|
context.shell_manager.next();
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::LeaveShell => {
|
CommandAction::LeaveShell => {
|
||||||
context.shell_manager.remove_at_current();
|
context.shell_manager.remove_at_current();
|
||||||
if context.shell_manager.is_empty() {
|
if context.shell_manager.is_empty() {
|
||||||
std::process::exit(0); // TODO: save history.txt
|
std::process::exit(0); // TODO: save history.txt
|
||||||
}
|
}
|
||||||
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(Value {
|
Ok(ReturnSuccess::Value(Value {
|
||||||
value: UntaggedValue::Error(err),
|
value: UntaggedValue::Error(err),
|
||||||
..
|
tag,
|
||||||
})) => {
|
})) => {
|
||||||
context.error(err.clone());
|
context.error(err.clone());
|
||||||
yield Err(err);
|
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(v)) => {
|
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||||
yielded = true;
|
|
||||||
yield Ok(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||||
yielded = true;
|
|
||||||
|
|
||||||
let doc = PrettyDebug::pretty_doc(&v);
|
let doc = PrettyDebug::pretty_doc(&v);
|
||||||
let mut buffer = termcolor::Buffer::ansi();
|
let mut buffer = termcolor::Buffer::ansi();
|
||||||
|
|
||||||
@@ -180,16 +251,17 @@ pub(crate) async fn run_internal_command(
|
|||||||
|
|
||||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||||
|
|
||||||
yield Ok(UntaggedValue::string(value).into_untagged_value())
|
InputStream::one(UntaggedValue::string(value).into_untagged_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
context.error(err);
|
context.error(err.clone());
|
||||||
break;
|
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.flatten()
|
||||||
Ok(stream.to_input_stream())
|
.take_while(|x| futures::future::ready(!x.is_error())),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
127
crates/nu-cli/src/commands/classified/maybe_text_codec.rs
Normal file
127
crates/nu-cli/src/commands/classified/maybe_text_codec.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
|
extern crate encoding_rs;
|
||||||
|
use encoding_rs::{CoderResult, Decoder, Encoding, UTF_8};
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
const OUTPUT_BUFFER_SIZE: usize = 8192;
|
||||||
|
#[cfg(test)]
|
||||||
|
const OUTPUT_BUFFER_SIZE: usize = 4;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum StringOrBinary {
|
||||||
|
String(String),
|
||||||
|
Binary(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MaybeTextCodec {
|
||||||
|
decoder: Decoder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeTextCodec {
|
||||||
|
// The constructor takes an Option<&'static Encoding>, because an absence of an encoding indicates that we want BOM sniffing enabled
|
||||||
|
pub fn new(encoding: Option<&'static Encoding>) -> Self {
|
||||||
|
let decoder = match encoding {
|
||||||
|
Some(e) => e.new_decoder_with_bom_removal(),
|
||||||
|
None => UTF_8.new_decoder(),
|
||||||
|
};
|
||||||
|
MaybeTextCodec { decoder }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MaybeTextCodec {
|
||||||
|
fn default() -> Self {
|
||||||
|
MaybeTextCodec {
|
||||||
|
decoder: UTF_8.new_decoder(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl futures_codec::Encoder for MaybeTextCodec {
|
||||||
|
type Item = StringOrBinary;
|
||||||
|
type Error = std::io::Error;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
|
match item {
|
||||||
|
StringOrBinary::String(s) => {
|
||||||
|
dst.reserve(s.len());
|
||||||
|
dst.put(s.as_bytes());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
StringOrBinary::Binary(b) => {
|
||||||
|
dst.reserve(b.len());
|
||||||
|
dst.put(Bytes::from(b));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl futures_codec::Decoder for MaybeTextCodec {
|
||||||
|
type Item = StringOrBinary;
|
||||||
|
type Error = ShellError;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
if src.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = String::with_capacity(OUTPUT_BUFFER_SIZE);
|
||||||
|
|
||||||
|
let (res, _read, replacements) = self.decoder.decode_to_string(src, &mut s, false);
|
||||||
|
|
||||||
|
let result = if replacements {
|
||||||
|
// If we had to make replacements when converting to utf8, fall back to binary
|
||||||
|
StringOrBinary::Binary(src.to_vec())
|
||||||
|
} else {
|
||||||
|
// If original buffer size is too small, we continue to allocate new Strings and append
|
||||||
|
// them to the result until the input buffer is smaller than the allocated String
|
||||||
|
if let CoderResult::OutputFull = res {
|
||||||
|
let mut buffer = String::with_capacity(OUTPUT_BUFFER_SIZE);
|
||||||
|
loop {
|
||||||
|
let (res, _read, _replacements) =
|
||||||
|
self.decoder
|
||||||
|
.decode_to_string(&src[s.len()..], &mut buffer, false);
|
||||||
|
s.push_str(&buffer);
|
||||||
|
|
||||||
|
if let CoderResult::InputEmpty = res {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringOrBinary::String(s)
|
||||||
|
};
|
||||||
|
|
||||||
|
src.clear();
|
||||||
|
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{MaybeTextCodec, StringOrBinary};
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use futures_codec::Decoder;
|
||||||
|
|
||||||
|
// TODO: Write some more tests
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_consume_all_bytes_from_source_when_temporary_buffer_overflows() {
|
||||||
|
let mut maybe_text = MaybeTextCodec::new(None);
|
||||||
|
let mut bytes = BytesMut::from("0123456789");
|
||||||
|
|
||||||
|
let text = maybe_text.decode(&mut bytes);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Ok(Some(StringOrBinary::String("0123456789".to_string()))),
|
||||||
|
text
|
||||||
|
);
|
||||||
|
assert!(bytes.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ mod dynamic;
|
|||||||
pub(crate) mod expr;
|
pub(crate) mod expr;
|
||||||
pub(crate) mod external;
|
pub(crate) mod external;
|
||||||
pub(crate) mod internal;
|
pub(crate) mod internal;
|
||||||
|
pub(crate) mod maybe_text_codec;
|
||||||
|
pub(crate) mod plugin;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) use dynamic::Command as DynamicCommand;
|
pub(crate) use dynamic::Command as DynamicCommand;
|
||||||
|
|||||||
467
crates/nu-cli/src/commands/classified/plugin.rs
Normal file
467
crates/nu-cli/src/commands/classified/plugin.rs
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
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;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Child, Command, Stdio};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "method")]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum NuResult {
|
||||||
|
response {
|
||||||
|
params: Result<VecDeque<ReturnValue>, ShellError>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 PluginFilter {
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
config: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for PluginFilter {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
self.config.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
&self.config.usage
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
run_filter(self.path.clone(), args, registry).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_filter(
|
||||||
|
path: String,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
trace!("filter_plugin :: {}", path);
|
||||||
|
let registry = registry.clone();
|
||||||
|
|
||||||
|
let scope = args.call_info.scope.clone();
|
||||||
|
|
||||||
|
let bos = futures::stream::iter(vec![
|
||||||
|
UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()
|
||||||
|
]);
|
||||||
|
let eos = futures::stream::iter(vec![
|
||||||
|
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
||||||
|
]);
|
||||||
|
|
||||||
|
let args = args.evaluate_once_with_scope(®istry, scope).await?;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
trace!("filtering :: {:?}", call_info);
|
||||||
|
|
||||||
|
Ok(bos
|
||||||
|
.chain(args.input)
|
||||||
|
.chain(eos)
|
||||||
|
.map(move |item| {
|
||||||
|
match item {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::BeginningOfStream),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Beginning of the stream
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
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(_) => {
|
||||||
|
return OutputStream::one(Err(ShellError::labeled_error(
|
||||||
|
"Could not load json from plugin",
|
||||||
|
"could not load json from plugin",
|
||||||
|
&call_info.name_tag,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(request_raw) => {
|
||||||
|
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
return OutputStream::one(Err(ShellError::unexpected(
|
||||||
|
format!("{}", err),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
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(),
|
||||||
|
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
|
||||||
|
.to_output_stream(),
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => OutputStream::one(Err(
|
||||||
|
ShellError::untagged_runtime_error(format!(
|
||||||
|
"Error while processing begin_filter response: {:?} {}",
|
||||||
|
e, input
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||||
|
format!("Error while reading begin_filter response: {:?}", e),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::EndOfStream),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// post stream contents
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
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(_) => {
|
||||||
|
return OutputStream::one(Err(ShellError::labeled_error(
|
||||||
|
"Could not load json from plugin",
|
||||||
|
"could not load json from plugin",
|
||||||
|
&call_info.name_tag,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(request_raw) => {
|
||||||
|
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
return OutputStream::one(Err(ShellError::unexpected(
|
||||||
|
format!("{}", err),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
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(),
|
||||||
|
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
|
||||||
|
.to_output_stream(),
|
||||||
|
},
|
||||||
|
Err(e) => futures::stream::iter(vec![Err(
|
||||||
|
ShellError::untagged_runtime_error(format!(
|
||||||
|
"Error while processing end_filter response: {:?} {}",
|
||||||
|
e, input
|
||||||
|
)),
|
||||||
|
)])
|
||||||
|
.to_output_stream(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
futures::stream::iter(vec![Err(ShellError::untagged_runtime_error(
|
||||||
|
format!("Error while reading end_filter response: {:?}", e),
|
||||||
|
))])
|
||||||
|
.to_output_stream()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
|
||||||
|
// TODO: Handle error
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||||
|
format!("Error while processing quit response: {:?}", e),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = child.wait();
|
||||||
|
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
||||||
|
v => {
|
||||||
|
// Stream contents
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
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());
|
||||||
|
// TODO: Handle error
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||||
|
format!("Error while processing filter response: {:?}", e),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
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(),
|
||||||
|
Err(e) => futures::stream::iter(vec![ReturnValue::Err(e)])
|
||||||
|
.to_output_stream(),
|
||||||
|
},
|
||||||
|
Err(e) => OutputStream::one(Err(
|
||||||
|
ShellError::untagged_runtime_error(format!(
|
||||||
|
"Error while processing filter response: {:?}\n== input ==\n{}",
|
||||||
|
e, input
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => OutputStream::one(Err(ShellError::untagged_runtime_error(
|
||||||
|
format!("Error while reading filter response: {:?}", e),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct PluginSink {
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
config: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for PluginSink {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
self.config.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
&self.config.usage
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
run_sink(self.path.clone(), args, registry).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_sink(
|
||||||
|
path: String,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let args = args.evaluate_once(®istry).await?;
|
||||||
|
let call_info = args.call_info.clone();
|
||||||
|
|
||||||
|
let input: Vec<Value> = args.input.collect().await;
|
||||||
|
|
||||||
|
let request = JsonRpc::new("sink", (call_info.clone(), input));
|
||||||
|
let request_raw = serde_json::to_string(&request);
|
||||||
|
if let Ok(request_raw) = request_raw {
|
||||||
|
if let Ok(mut tmpfile) = tempfile::NamedTempFile::new() {
|
||||||
|
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||||
|
let _ = tmpfile.flush();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
Ok(OutputStream::empty())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Could not create process for sink command",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Could not open file to send sink command message",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Could not create message to sink command",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"clears the terminal"
|
"Clears the terminal"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
@@ -43,15 +43,3 @@ impl WholeStreamCommand for Clear {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Clear;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Clear {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
@@ -32,11 +32,18 @@ impl WholeStreamCommand for Clip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Save text to the clipboard",
|
description: "Save text to the clipboard",
|
||||||
example: "echo 'secret value' | clip",
|
example: "echo 'secret value' | clip",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Save numbers to the clipboard",
|
||||||
|
example: "random integer 10000000..99999999 | clip",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,16 +68,14 @@ pub async fn clip(
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let string: String = match i.as_string() {
|
let string: String = i.convert_to_string();
|
||||||
Ok(string) => string.to_string(),
|
if string.is_empty() {
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Given non-string data",
|
"Unable to convert to string",
|
||||||
"expected strings from pipeline",
|
"Unable to convert to string",
|
||||||
name,
|
name,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
new_copy_data.push_str(&string);
|
new_copy_data.push_str(&string);
|
||||||
}
|
}
|
||||||
@@ -99,11 +104,12 @@ pub async fn clip(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Clip;
|
use super::Clip;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Clip {})
|
Ok(test_examples(Clip {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::help::get_help;
|
use crate::commands::help::get_help;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::deserializer::ConfigDeserializer;
|
use crate::deserializer::ConfigDeserializer;
|
||||||
use crate::evaluate::evaluate_args::evaluate_args;
|
use crate::evaluate::evaluate_args::evaluate_args;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -17,27 +17,12 @@ use std::sync::atomic::AtomicBool;
|
|||||||
pub struct UnevaluatedCallInfo {
|
pub struct UnevaluatedCallInfo {
|
||||||
pub args: hir::Call,
|
pub args: hir::Call,
|
||||||
pub name_tag: Tag,
|
pub name_tag: Tag,
|
||||||
pub scope: Scope,
|
pub scope: Arc<Scope>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnevaluatedCallInfo {
|
impl UnevaluatedCallInfo {
|
||||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
let args = evaluate_args(&self.args, registry, self.scope.clone()).await?;
|
||||||
|
|
||||||
Ok(CallInfo {
|
|
||||||
args,
|
|
||||||
name_tag: self.name_tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn evaluate_with_new_it(
|
|
||||||
self,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
it: &Value,
|
|
||||||
) -> Result<CallInfo, ShellError> {
|
|
||||||
let mut scope = self.scope.clone();
|
|
||||||
scope.it = it.clone();
|
|
||||||
let args = evaluate_args(&self.args, registry, &scope).await?;
|
|
||||||
|
|
||||||
Ok(CallInfo {
|
Ok(CallInfo {
|
||||||
args,
|
args,
|
||||||
@@ -115,7 +100,7 @@ impl CommandArgs {
|
|||||||
pub async fn evaluate_once_with_scope(
|
pub async fn evaluate_once_with_scope(
|
||||||
self,
|
self,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
scope: &Scope,
|
scope: Arc<Scope>,
|
||||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||||
let host = self.host.clone();
|
let host = self.host.clone();
|
||||||
let ctrl_c = self.ctrl_c.clone();
|
let ctrl_c = self.ctrl_c.clone();
|
||||||
@@ -215,37 +200,6 @@ impl EvaluatedWholeStreamCommandArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Getters)]
|
|
||||||
#[get = "pub"]
|
|
||||||
pub struct EvaluatedFilterCommandArgs {
|
|
||||||
args: EvaluatedCommandArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for EvaluatedFilterCommandArgs {
|
|
||||||
type Target = EvaluatedCommandArgs;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EvaluatedFilterCommandArgs {
|
|
||||||
pub fn new(
|
|
||||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
|
||||||
ctrl_c: Arc<AtomicBool>,
|
|
||||||
shell_manager: ShellManager,
|
|
||||||
call_info: CallInfo,
|
|
||||||
) -> EvaluatedFilterCommandArgs {
|
|
||||||
EvaluatedFilterCommandArgs {
|
|
||||||
args: EvaluatedCommandArgs {
|
|
||||||
host,
|
|
||||||
ctrl_c,
|
|
||||||
shell_manager,
|
|
||||||
call_info,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Getters, new)]
|
#[derive(Getters, new)]
|
||||||
#[get = "pub(crate)"]
|
#[get = "pub(crate)"]
|
||||||
pub struct EvaluatedCommandArgs {
|
pub struct EvaluatedCommandArgs {
|
||||||
@@ -262,10 +216,10 @@ impl EvaluatedCommandArgs {
|
|||||||
|
|
||||||
/// Get the nth positional argument, error if not possible
|
/// Get the nth positional argument, error if not possible
|
||||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||||
match self.call_info.args.nth(pos) {
|
self.call_info
|
||||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
.args
|
||||||
Some(item) => Ok(item),
|
.nth(pos)
|
||||||
}
|
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||||
@@ -303,6 +257,11 @@ pub trait WholeStreamCommand: Send + Sync {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands that are not meant to be run by users
|
||||||
|
fn is_internal(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@@ -343,18 +302,23 @@ impl Command {
|
|||||||
self.0.usage()
|
self.0.usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
|
pub fn examples(&self) -> Vec<Example> {
|
||||||
|
self.0.examples()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
if args.call_info.switch_present("help") {
|
if args.call_info.switch_present("help") {
|
||||||
let cl = self.0.clone();
|
let cl = self.0.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||||
)))
|
))))
|
||||||
} else {
|
} else {
|
||||||
match self.0.run(args, registry).await {
|
self.0.run(args, registry).await
|
||||||
Ok(stream) => stream,
|
|
||||||
Err(err) => OutputStream::one(Err(err)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,72 +326,15 @@ impl Command {
|
|||||||
self.0.is_binary()
|
self.0.is_binary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_internal(&self) -> bool {
|
||||||
|
self.0.is_internal()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||||
&*self.0
|
&*self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FnFilterCommand {
|
|
||||||
name: String,
|
|
||||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for FnFilterCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"usage"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
CommandArgs {
|
|
||||||
host,
|
|
||||||
ctrl_c,
|
|
||||||
shell_manager,
|
|
||||||
call_info,
|
|
||||||
mut input,
|
|
||||||
..
|
|
||||||
}: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
|
|
||||||
let registry: CommandRegistry = registry.clone();
|
|
||||||
let func = self.func;
|
|
||||||
|
|
||||||
let stream = async_stream! {
|
|
||||||
while let Some(it) = input.next().await {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let call_info = match call_info.clone().evaluate_with_new_it(®istry, &it).await {
|
|
||||||
Err(err) => { yield Err(err); return; },
|
|
||||||
Ok(args) => args,
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = EvaluatedFilterCommandArgs::new(
|
|
||||||
host.clone(),
|
|
||||||
ctrl_c.clone(),
|
|
||||||
shell_manager.clone(),
|
|
||||||
call_info,
|
|
||||||
);
|
|
||||||
|
|
||||||
match func(args) {
|
|
||||||
Err(err) => yield Err(err),
|
|
||||||
Ok(mut stream) => {
|
|
||||||
while let Some(value) = stream.values.next().await {
|
|
||||||
yield value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||||
Command(Arc::new(command))
|
Command(Arc::new(command))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
@@ -37,22 +37,11 @@ impl WholeStreamCommand for Compact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![Example {
|
||||||
Example {
|
|
||||||
description: "Filter out all null entries in a list",
|
|
||||||
example: "echo [1 2 $null 3 $null $null] | compact target",
|
|
||||||
result: Some(vec![
|
|
||||||
UntaggedValue::int(1).into(),
|
|
||||||
UntaggedValue::int(2).into(),
|
|
||||||
UntaggedValue::int(3).into(),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Filter out all directory entries having no 'target'",
|
description: "Filter out all directory entries having no 'target'",
|
||||||
example: "ls -af | compact target",
|
example: "ls -la | compact target",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +84,12 @@ pub async fn compact(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Compact;
|
use super::Compact;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Compact {})
|
Ok(test_examples(Compact {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::data::config;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub struct Config;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ConfigArgs {
|
|
||||||
load: Option<Tagged<PathBuf>>,
|
|
||||||
set: Option<(Tagged<String>, Value)>,
|
|
||||||
set_into: Option<Tagged<String>>,
|
|
||||||
get: Option<Tagged<String>>,
|
|
||||||
clear: Tagged<bool>,
|
|
||||||
remove: Option<Tagged<String>>,
|
|
||||||
path: Tagged<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Config {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"config"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("config")
|
|
||||||
.named(
|
|
||||||
"load",
|
|
||||||
SyntaxShape::Path,
|
|
||||||
"load the config from the path given",
|
|
||||||
Some('l'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"set",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"set a value in the config, eg) --set [key value]",
|
|
||||||
Some('s'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"set_into",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"sets a variable from values in the pipeline",
|
|
||||||
Some('i'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"get",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"get a value from the config",
|
|
||||||
Some('g'),
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"remove",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"remove a value from the config",
|
|
||||||
Some('r'),
|
|
||||||
)
|
|
||||||
.switch("clear", "clear the config", Some('c'))
|
|
||||||
.switch("path", "return the path to the config file", Some('p'))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Configuration management."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
config(args, registry).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "See all config values",
|
|
||||||
example: "config",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Set completion_mode to circular",
|
|
||||||
example: "config --set [completion_mode circular]",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Store the contents of the pipeline as a path",
|
|
||||||
example: "echo ['/usr/bin' '/bin'] | config --set_into path",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Get the current startup commands",
|
|
||||||
example: "config --get startup",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Remove the startup commands",
|
|
||||||
example: "config --remove startup",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Clear the config (be careful!)",
|
|
||||||
example: "config --clear",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Get the path to the current config file",
|
|
||||||
example: "config --path",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn config(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let name_span = args.call_info.name_tag.clone();
|
|
||||||
let name = args.call_info.name_tag.clone();
|
|
||||||
let registry = registry.clone();
|
|
||||||
|
|
||||||
let (
|
|
||||||
ConfigArgs {
|
|
||||||
load,
|
|
||||||
set,
|
|
||||||
set_into,
|
|
||||||
get,
|
|
||||||
clear,
|
|
||||||
remove,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
let configuration = if let Some(supplied) = load {
|
|
||||||
Some(supplied.item().clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = crate::data::config::read(name_span, &configuration)?;
|
|
||||||
|
|
||||||
Ok(if let Some(v) = get {
|
|
||||||
let key = v.to_string();
|
|
||||||
let value = result
|
|
||||||
.get(&key)
|
|
||||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?;
|
|
||||||
|
|
||||||
match value {
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Table(list),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let list: Vec<_> = list
|
|
||||||
.iter()
|
|
||||||
.map(|x| ReturnSuccess::value(x.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
futures::stream::iter(list).to_output_stream()
|
|
||||||
}
|
|
||||||
x => {
|
|
||||||
let x = x.clone();
|
|
||||||
OutputStream::one(ReturnSuccess::value(x))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some((key, value)) = set {
|
|
||||||
result.insert(key.to_string(), value.clone());
|
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
|
||||||
))
|
|
||||||
} else if let Some(v) = set_into {
|
|
||||||
let rows: Vec<Value> = input.collect().await;
|
|
||||||
let key = v.to_string();
|
|
||||||
|
|
||||||
if rows.is_empty() {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"No values given for set_into",
|
|
||||||
"needs value(s) from pipeline",
|
|
||||||
v.tag(),
|
|
||||||
));
|
|
||||||
} else if rows.len() == 1 {
|
|
||||||
// A single value
|
|
||||||
let value = &rows[0];
|
|
||||||
|
|
||||||
result.insert(key, value.clone());
|
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
// Take in the pipeline as a table
|
|
||||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
|
||||||
|
|
||||||
result.insert(key, value);
|
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else if let Tagged { item: true, tag } = clear {
|
|
||||||
result.clear();
|
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(tag),
|
|
||||||
))
|
|
||||||
} else if let Tagged { item: true, tag } = path {
|
|
||||||
let path = config::default_path_for(&configuration)?;
|
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::Primitive(Primitive::Path(path)).into_value(tag),
|
|
||||||
))
|
|
||||||
} else if let Some(v) = remove {
|
|
||||||
let key = v.to_string();
|
|
||||||
|
|
||||||
if result.contains_key(&key) {
|
|
||||||
result.swap_remove(&key);
|
|
||||||
config::write(&result, &configuration)?;
|
|
||||||
futures::stream::iter(vec![ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(v.tag()),
|
|
||||||
)])
|
|
||||||
.to_output_stream()
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"Key does not exist in config",
|
|
||||||
"key",
|
|
||||||
v.tag(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
futures::stream::iter(vec![ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
|
||||||
)])
|
|
||||||
.to_output_stream()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Config;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Config {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
57
crates/nu-cli/src/commands/config/clear.rs
Normal file
57
crates/nu-cli/src/commands/config/clear.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config clear"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config clear")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"clear the config"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
clear(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Clear the config (be careful!)",
|
||||||
|
example: "config clear",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear(
|
||||||
|
args: CommandArgs,
|
||||||
|
_registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
|
// existing config
|
||||||
|
let mut result = nu_data::config::read(name_span, &None)?;
|
||||||
|
|
||||||
|
result.clear();
|
||||||
|
|
||||||
|
config::write(&result, &None)?;
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
37
crates/nu-cli/src/commands/config/command.rs
Normal file
37
crates/nu-cli/src/commands/config/command.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{CommandArgs, CommandRegistry, OutputStream};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Configuration management."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
_registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
let name = args.call_info.name_tag;
|
||||||
|
let result = nu_data::config::read(name_span, &None)?;
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
|
)])
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
76
crates/nu-cli/src/commands/config/get.rs
Normal file
76
crates/nu-cli/src/commands/config/get.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct GetArgs {
|
||||||
|
path: ColumnPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config get"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config get").required(
|
||||||
|
"get",
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"value to get from the config",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Gets a value from the config"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
get(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Get the current startup commands",
|
||||||
|
example: "config get startup",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
|
let (GetArgs { path }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
|
// existing config
|
||||||
|
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
|
||||||
|
|
||||||
|
let value = crate::commands::get::get_column_path(&path, &result)?;
|
||||||
|
|
||||||
|
Ok(match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Table(list),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let list: Vec<_> = list
|
||||||
|
.iter()
|
||||||
|
.map(|x| ReturnSuccess::value(x.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
futures::stream::iter(list).to_output_stream()
|
||||||
|
}
|
||||||
|
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||||
|
})
|
||||||
|
}
|
||||||
59
crates/nu-cli/src/commands/config/load.rs
Normal file
59
crates/nu-cli/src/commands/config/load.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoadArgs {
|
||||||
|
load: Tagged<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config load"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config load").required(
|
||||||
|
"load",
|
||||||
|
SyntaxShape::Path,
|
||||||
|
"Path to load the config from",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Loads the config from the path given"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
set(args, registry).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name = args.call_info.name_tag.clone();
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
let (LoadArgs { load }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let configuration = load.item().clone();
|
||||||
|
|
||||||
|
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
|
)])
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
17
crates/nu-cli/src/commands/config/mod.rs
Normal file
17
crates/nu-cli/src/commands/config/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
pub mod clear;
|
||||||
|
pub mod command;
|
||||||
|
pub mod get;
|
||||||
|
pub mod load;
|
||||||
|
pub mod path;
|
||||||
|
pub mod remove;
|
||||||
|
pub mod set;
|
||||||
|
pub mod set_into;
|
||||||
|
|
||||||
|
pub use clear::SubCommand as ConfigClear;
|
||||||
|
pub use command::Command as Config;
|
||||||
|
pub use get::SubCommand as ConfigGet;
|
||||||
|
pub use load::SubCommand as ConfigLoad;
|
||||||
|
pub use path::SubCommand as ConfigPath;
|
||||||
|
pub use remove::SubCommand as ConfigRemove;
|
||||||
|
pub use set::SubCommand as ConfigSet;
|
||||||
|
pub use set_into::SubCommand as ConfigSetInto;
|
||||||
49
crates/nu-cli/src/commands/config/path.rs
Normal file
49
crates/nu-cli/src/commands/config/path.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config path"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"return the path to the config file"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
path(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Get the path to the current config file",
|
||||||
|
example: "config path",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn path(
|
||||||
|
args: CommandArgs,
|
||||||
|
_registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let path = config::default_path()?;
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Primitive(Primitive::Path(path)).into_value(args.call_info.name_tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
75
crates/nu-cli/src/commands/config/remove.rs
Normal file
75
crates/nu-cli/src/commands/config/remove.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RemoveArgs {
|
||||||
|
remove: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config remove"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config remove").required(
|
||||||
|
"remove",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"remove a value from the config",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Removes a value from the config"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
remove(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Remove the startup commands",
|
||||||
|
example: "config remove startup",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let mut result = nu_data::config::read(name_span, &None)?;
|
||||||
|
|
||||||
|
let key = remove.to_string();
|
||||||
|
|
||||||
|
if result.contains_key(&key) {
|
||||||
|
result.swap_remove(&key);
|
||||||
|
config::write(&result, &None)?;
|
||||||
|
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(remove.tag()),
|
||||||
|
)])
|
||||||
|
.to_output_stream())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Key does not exist in config",
|
||||||
|
"key",
|
||||||
|
remove.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
97
crates/nu-cli/src/commands/config/set.rs
Normal file
97
crates/nu-cli/src/commands/config/set.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SetArgs {
|
||||||
|
path: ColumnPath,
|
||||||
|
value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config set"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config set")
|
||||||
|
.required("key", SyntaxShape::ColumnPath, "variable name to set")
|
||||||
|
.required("value", SyntaxShape::Any, "value to use")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Sets a value in the config"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
set(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Set auto pivoting",
|
||||||
|
example: "config set pivot_mode always",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set line editor options",
|
||||||
|
example: "config set line_editor [[edit_mode, completion_type]; [emacs circular]]",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set coloring options",
|
||||||
|
example: "config set color_config [[header_align header_bold]; [left $true]]",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set nested options",
|
||||||
|
example: "config set color_config.header_color white",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
|
let (SetArgs { path, mut value }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
|
// existing config
|
||||||
|
let raw_entries = nu_data::config::read(&name_tag, &None)?;
|
||||||
|
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
|
||||||
|
|
||||||
|
if let UntaggedValue::Table(rows) = &value.value {
|
||||||
|
if rows.len() == 1 && rows[0].is_row() {
|
||||||
|
value = rows[0].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
||||||
|
Ok(Value {
|
||||||
|
value: UntaggedValue::Row(changes),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
config::write(&changes.entries, &None)?;
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(changes).into_value(name_tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Ok(_) => Ok(OutputStream::empty()),
|
||||||
|
Err(reason) => Err(reason),
|
||||||
|
}
|
||||||
|
}
|
||||||
98
crates/nu-cli/src/commands/config/set_into.rs
Normal file
98
crates/nu-cli/src/commands/config/set_into.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SetIntoArgs {
|
||||||
|
set_into: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config set_into"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("config set_into").required(
|
||||||
|
"set_into",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"sets a variable from values in the pipeline",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Sets a value in the config"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
set_into(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Store the contents of the pipeline as a path",
|
||||||
|
example: "echo ['/usr/bin' '/bin'] | config set_into path",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_into(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
let name = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let (SetIntoArgs { set_into: v }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||||
|
// existing config
|
||||||
|
let mut result = nu_data::config::read(name_span, &None)?;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
let key = v.to_string();
|
||||||
|
|
||||||
|
Ok(if rows.is_empty() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"No values given for set_into",
|
||||||
|
"needs value(s) from pipeline",
|
||||||
|
v.tag(),
|
||||||
|
));
|
||||||
|
} else if rows.len() == 1 {
|
||||||
|
// A single value
|
||||||
|
let value = &rows[0];
|
||||||
|
|
||||||
|
result.insert(key, value.clone());
|
||||||
|
|
||||||
|
config::write(&result, &configuration)?;
|
||||||
|
|
||||||
|
OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Take in the pipeline as a table
|
||||||
|
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||||
|
|
||||||
|
result.insert(key, value);
|
||||||
|
|
||||||
|
config::write(&result, &configuration)?;
|
||||||
|
|
||||||
|
OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
358
crates/nu-cli/src/commands/constants.rs
Normal file
358
crates/nu-cli/src/commands/constants.rs
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
pub const BAT_LANGUAGES: &[&str] = &[
|
||||||
|
"as",
|
||||||
|
"csv",
|
||||||
|
"tsv",
|
||||||
|
"applescript",
|
||||||
|
"script editor",
|
||||||
|
"s",
|
||||||
|
"S",
|
||||||
|
"adoc",
|
||||||
|
"asciidoc",
|
||||||
|
"asc",
|
||||||
|
"asa",
|
||||||
|
"yasm",
|
||||||
|
"nasm",
|
||||||
|
"asm",
|
||||||
|
"inc",
|
||||||
|
"mac",
|
||||||
|
"awk",
|
||||||
|
"bat",
|
||||||
|
"cmd",
|
||||||
|
"bib",
|
||||||
|
"sh",
|
||||||
|
"bash",
|
||||||
|
"zsh",
|
||||||
|
".bash_aliases",
|
||||||
|
".bash_completions",
|
||||||
|
".bash_functions",
|
||||||
|
".bash_login",
|
||||||
|
".bash_logout",
|
||||||
|
".bash_profile",
|
||||||
|
".bash_variables",
|
||||||
|
".bashrc",
|
||||||
|
".profile",
|
||||||
|
".textmate_init",
|
||||||
|
".zshrc",
|
||||||
|
"PKGBUILD",
|
||||||
|
".ebuild",
|
||||||
|
".eclass",
|
||||||
|
"c",
|
||||||
|
"h",
|
||||||
|
"cs",
|
||||||
|
"csx",
|
||||||
|
"cpp",
|
||||||
|
"cc",
|
||||||
|
"cp",
|
||||||
|
"cxx",
|
||||||
|
"c++",
|
||||||
|
"C",
|
||||||
|
"h",
|
||||||
|
"hh",
|
||||||
|
"hpp",
|
||||||
|
"hxx",
|
||||||
|
"h++",
|
||||||
|
"inl",
|
||||||
|
"ipp",
|
||||||
|
"cabal",
|
||||||
|
"clj",
|
||||||
|
"cljc",
|
||||||
|
"cljs",
|
||||||
|
"edn",
|
||||||
|
"CMakeLists.txt",
|
||||||
|
"cmake",
|
||||||
|
"h.in",
|
||||||
|
"hh.in",
|
||||||
|
"hpp.in",
|
||||||
|
"hxx.in",
|
||||||
|
"h++.in",
|
||||||
|
"CMakeCache.txt",
|
||||||
|
"cr",
|
||||||
|
"css",
|
||||||
|
"css.erb",
|
||||||
|
"css.liquid",
|
||||||
|
"d",
|
||||||
|
"di",
|
||||||
|
"dart",
|
||||||
|
"diff",
|
||||||
|
"patch",
|
||||||
|
"Dockerfile",
|
||||||
|
"dockerfile",
|
||||||
|
"ex",
|
||||||
|
"exs",
|
||||||
|
"elm",
|
||||||
|
"erl",
|
||||||
|
"hrl",
|
||||||
|
"Emakefile",
|
||||||
|
"emakefile",
|
||||||
|
"fs",
|
||||||
|
"fsi",
|
||||||
|
"fsx",
|
||||||
|
"fs",
|
||||||
|
"fsi",
|
||||||
|
"fsx",
|
||||||
|
"fish",
|
||||||
|
"attributes",
|
||||||
|
"gitattributes",
|
||||||
|
".gitattributes",
|
||||||
|
"COMMIT_EDITMSG",
|
||||||
|
"MERGE_MSG",
|
||||||
|
"TAG_EDITMSG",
|
||||||
|
"gitconfig",
|
||||||
|
".gitconfig",
|
||||||
|
".gitmodules",
|
||||||
|
"exclude",
|
||||||
|
"gitignore",
|
||||||
|
".gitignore",
|
||||||
|
".git",
|
||||||
|
"gitlog",
|
||||||
|
"git-rebase-todo",
|
||||||
|
"go",
|
||||||
|
"dot",
|
||||||
|
"DOT",
|
||||||
|
"gv",
|
||||||
|
"groovy",
|
||||||
|
"gvy",
|
||||||
|
"gradle",
|
||||||
|
"Jenkinsfile",
|
||||||
|
"hs",
|
||||||
|
"hs",
|
||||||
|
"hsc",
|
||||||
|
"show-nonprintable",
|
||||||
|
"html",
|
||||||
|
"htm",
|
||||||
|
"shtml",
|
||||||
|
"xhtml",
|
||||||
|
"asp",
|
||||||
|
"html.eex",
|
||||||
|
"yaws",
|
||||||
|
"rails",
|
||||||
|
"rhtml",
|
||||||
|
"erb",
|
||||||
|
"html.erb",
|
||||||
|
"adp",
|
||||||
|
"twig",
|
||||||
|
"html.twig",
|
||||||
|
"ini",
|
||||||
|
"INI",
|
||||||
|
"INF",
|
||||||
|
"reg",
|
||||||
|
"REG",
|
||||||
|
"lng",
|
||||||
|
"cfg",
|
||||||
|
"CFG",
|
||||||
|
"desktop",
|
||||||
|
"url",
|
||||||
|
"URL",
|
||||||
|
".editorconfig",
|
||||||
|
".hgrc",
|
||||||
|
"hgrc",
|
||||||
|
"java",
|
||||||
|
"bsh",
|
||||||
|
"properties",
|
||||||
|
"jsp",
|
||||||
|
"js",
|
||||||
|
"htc",
|
||||||
|
"js",
|
||||||
|
"jsx",
|
||||||
|
"babel",
|
||||||
|
"es6",
|
||||||
|
"js.erb",
|
||||||
|
"json",
|
||||||
|
"sublime-settings",
|
||||||
|
"sublime-menu",
|
||||||
|
"sublime-keymap",
|
||||||
|
"sublime-mousemap",
|
||||||
|
"sublime-theme",
|
||||||
|
"sublime-build",
|
||||||
|
"sublime-project",
|
||||||
|
"sublime-completions",
|
||||||
|
"sublime-commands",
|
||||||
|
"sublime-macro",
|
||||||
|
"sublime-color-scheme",
|
||||||
|
"ipynb",
|
||||||
|
"Pipfile.lock",
|
||||||
|
"jsonnet",
|
||||||
|
"libsonnet",
|
||||||
|
"libjsonnet",
|
||||||
|
"jl",
|
||||||
|
"kt",
|
||||||
|
"kts",
|
||||||
|
"tex",
|
||||||
|
"ltx",
|
||||||
|
"less",
|
||||||
|
"css.less",
|
||||||
|
"lisp",
|
||||||
|
"cl",
|
||||||
|
"clisp",
|
||||||
|
"l",
|
||||||
|
"mud",
|
||||||
|
"el",
|
||||||
|
"scm",
|
||||||
|
"ss",
|
||||||
|
"lsp",
|
||||||
|
"fasl",
|
||||||
|
"lhs",
|
||||||
|
"lua",
|
||||||
|
"make",
|
||||||
|
"GNUmakefile",
|
||||||
|
"makefile",
|
||||||
|
"Makefile",
|
||||||
|
"makefile.am",
|
||||||
|
"Makefile.am",
|
||||||
|
"makefile.in",
|
||||||
|
"Makefile.in",
|
||||||
|
"OCamlMakefile",
|
||||||
|
"mak",
|
||||||
|
"mk",
|
||||||
|
"md",
|
||||||
|
"mdown",
|
||||||
|
"markdown",
|
||||||
|
"markdn",
|
||||||
|
"matlab",
|
||||||
|
"build",
|
||||||
|
"nix",
|
||||||
|
"m",
|
||||||
|
"h",
|
||||||
|
"mm",
|
||||||
|
"M",
|
||||||
|
"h",
|
||||||
|
"ml",
|
||||||
|
"mli",
|
||||||
|
"mll",
|
||||||
|
"mly",
|
||||||
|
"pas",
|
||||||
|
"p",
|
||||||
|
"dpr",
|
||||||
|
"pl",
|
||||||
|
"pm",
|
||||||
|
"pod",
|
||||||
|
"t",
|
||||||
|
"PL",
|
||||||
|
"php",
|
||||||
|
"php3",
|
||||||
|
"php4",
|
||||||
|
"php5",
|
||||||
|
"php7",
|
||||||
|
"phps",
|
||||||
|
"phpt",
|
||||||
|
"phtml",
|
||||||
|
"txt",
|
||||||
|
"ps1",
|
||||||
|
"psm1",
|
||||||
|
"psd1",
|
||||||
|
"proto",
|
||||||
|
"protodevel",
|
||||||
|
"pb.txt",
|
||||||
|
"proto.text",
|
||||||
|
"textpb",
|
||||||
|
"pbtxt",
|
||||||
|
"prototxt",
|
||||||
|
"pp",
|
||||||
|
"epp",
|
||||||
|
"purs",
|
||||||
|
"py",
|
||||||
|
"py3",
|
||||||
|
"pyw",
|
||||||
|
"pyi",
|
||||||
|
"pyx",
|
||||||
|
"pyx.in",
|
||||||
|
"pxd",
|
||||||
|
"pxd.in",
|
||||||
|
"pxi",
|
||||||
|
"pxi.in",
|
||||||
|
"rpy",
|
||||||
|
"cpy",
|
||||||
|
"SConstruct",
|
||||||
|
"Sconstruct",
|
||||||
|
"sconstruct",
|
||||||
|
"SConscript",
|
||||||
|
"gyp",
|
||||||
|
"gypi",
|
||||||
|
"Snakefile",
|
||||||
|
"wscript",
|
||||||
|
"R",
|
||||||
|
"r",
|
||||||
|
"s",
|
||||||
|
"S",
|
||||||
|
"Rprofile",
|
||||||
|
"rd",
|
||||||
|
"re",
|
||||||
|
"rst",
|
||||||
|
"rest",
|
||||||
|
"robot",
|
||||||
|
"rb",
|
||||||
|
"Appfile",
|
||||||
|
"Appraisals",
|
||||||
|
"Berksfile",
|
||||||
|
"Brewfile",
|
||||||
|
"capfile",
|
||||||
|
"cgi",
|
||||||
|
"Cheffile",
|
||||||
|
"config.ru",
|
||||||
|
"Deliverfile",
|
||||||
|
"Fastfile",
|
||||||
|
"fcgi",
|
||||||
|
"Gemfile",
|
||||||
|
"gemspec",
|
||||||
|
"Guardfile",
|
||||||
|
"irbrc",
|
||||||
|
"jbuilder",
|
||||||
|
"Podfile",
|
||||||
|
"podspec",
|
||||||
|
"prawn",
|
||||||
|
"rabl",
|
||||||
|
"rake",
|
||||||
|
"Rakefile",
|
||||||
|
"Rantfile",
|
||||||
|
"rbx",
|
||||||
|
"rjs",
|
||||||
|
"ruby.rail",
|
||||||
|
"Scanfile",
|
||||||
|
"simplecov",
|
||||||
|
"Snapfile",
|
||||||
|
"thor",
|
||||||
|
"Thorfile",
|
||||||
|
"Vagrantfile",
|
||||||
|
"haml",
|
||||||
|
"sass",
|
||||||
|
"rxml",
|
||||||
|
"builder",
|
||||||
|
"rs",
|
||||||
|
"scala",
|
||||||
|
"sbt",
|
||||||
|
"sql",
|
||||||
|
"ddl",
|
||||||
|
"dml",
|
||||||
|
"erbsql",
|
||||||
|
"sql.erb",
|
||||||
|
"swift",
|
||||||
|
"log",
|
||||||
|
"tcl",
|
||||||
|
"tf",
|
||||||
|
"tfvars",
|
||||||
|
"hcl",
|
||||||
|
"sty",
|
||||||
|
"cls",
|
||||||
|
"textile",
|
||||||
|
"toml",
|
||||||
|
"tml",
|
||||||
|
"Cargo.lock",
|
||||||
|
"Gopkg.lock",
|
||||||
|
"Pipfile",
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"varlink",
|
||||||
|
"vim",
|
||||||
|
".vimrc",
|
||||||
|
"xml",
|
||||||
|
"xsd",
|
||||||
|
"xslt",
|
||||||
|
"tld",
|
||||||
|
"dtml",
|
||||||
|
"rss",
|
||||||
|
"opml",
|
||||||
|
"svg",
|
||||||
|
"yaml",
|
||||||
|
"yml",
|
||||||
|
"sublime-syntax",
|
||||||
|
];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
@@ -7,6 +7,11 @@ use nu_protocol::{Signature, UntaggedValue, Value};
|
|||||||
|
|
||||||
pub struct Count;
|
pub struct Count;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CountArgs {
|
||||||
|
column: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Count {
|
impl WholeStreamCommand for Count {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@@ -14,7 +19,11 @@ impl WholeStreamCommand for Count {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("count")
|
Signature::build("count").switch(
|
||||||
|
"column",
|
||||||
|
"Calculate number of columns in table",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@@ -24,33 +33,59 @@ impl WholeStreamCommand for Count {
|
|||||||
async fn run(
|
async fn run(
|
||||||
&self,
|
&self,
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
_registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let rows: Vec<Value> = args.input.collect().await;
|
let (CountArgs { column }, input) = args.process(®istry).await?;
|
||||||
|
let rows: Vec<Value> = input.collect().await;
|
||||||
|
|
||||||
Ok(OutputStream::one(
|
let count = if column {
|
||||||
UntaggedValue::int(rows.len()).into_value(name),
|
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> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Count the number of entries in a list",
|
description: "Count the number of entries in a list",
|
||||||
example: "echo [1 2 3 4 5] | count",
|
example: "echo [1 2 3 4 5] | count",
|
||||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Count the number of columns in the calendar table",
|
||||||
|
example: "cal | count -c",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Count;
|
use super::Count;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Count {})
|
Ok(test_examples(Count {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
@@ -66,11 +66,12 @@ impl WholeStreamCommand for Cpy {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Cpy;
|
use super::Cpy;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Cpy {})
|
Ok(test_examples(Cpy {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +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, 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'))
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag) -> Value
|
|
||||||
where
|
|
||||||
T::Offset: Display,
|
|
||||||
{
|
|
||||||
let mut indexmap = IndexMap::new();
|
|
||||||
|
|
||||||
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),
|
|
||||||
);
|
|
||||||
|
|
||||||
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 value = if args.has("utc") {
|
|
||||||
let utc: DateTime<Utc> = Utc::now();
|
|
||||||
date_to_value(utc, tag)
|
|
||||||
} else {
|
|
||||||
let local: DateTime<Local> = Local::now();
|
|
||||||
date_to_value(local, tag)
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
crates/nu-cli/src/commands/date/command.rs
Normal file
47
crates/nu-cli/src/commands/date/command.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Command {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
76
crates/nu-cli/src/commands/date/format.rs
Normal file
76
crates/nu-cli/src/commands/date/format.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
63
crates/nu-cli/src/commands/date/now.rs
Normal file
63
crates/nu-cli/src/commands/date/now.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;
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
63
crates/nu-cli/src/commands/date/utc.rs
Normal file
63
crates/nu-cli/src/commands/date/utc.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -55,11 +55,12 @@ async fn debug_value(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Debug;
|
use super::Debug;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Debug {})
|
Ok(test_examples(Debug {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
@@ -45,7 +45,7 @@ impl WholeStreamCommand for Default {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Give a default 'target' to all file entries",
|
description: "Give a default 'target' to all file entries",
|
||||||
example: "ls -af | default target 'nothing'",
|
example: "ls -la | default target 'nothing'",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@@ -60,13 +60,13 @@ async fn default(
|
|||||||
|
|
||||||
Ok(input
|
Ok(input
|
||||||
.map(move |item| {
|
.map(move |item| {
|
||||||
let should_add = match item {
|
let should_add = matches!(
|
||||||
|
item,
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Row(ref r),
|
value: UntaggedValue::Row(ref r),
|
||||||
..
|
..
|
||||||
} => r.get_data(&column.item).borrow().is_none(),
|
} if r.get_data(&column.item).borrow().is_none()
|
||||||
_ => false,
|
);
|
||||||
};
|
|
||||||
|
|
||||||
if should_add {
|
if should_add {
|
||||||
match item.insert_data_at_path(&column.item, value.clone()) {
|
match item.insert_data_at_path(&column.item, value.clone()) {
|
||||||
@@ -83,11 +83,12 @@ async fn default(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Default;
|
use super::Default;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Default {})
|
Ok(test_examples(Default {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
crates/nu-cli/src/commands/describe.rs
Normal file
61
crates/nu-cli/src/commands/describe.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Describe;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DescribeArgs {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Describe {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"describe"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("describe")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Describes the objects in the stream."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
describe(args, registry).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn describe(
|
||||||
|
args: CommandArgs,
|
||||||
|
_registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
Ok(args
|
||||||
|
.input
|
||||||
|
.map(|row| {
|
||||||
|
let name = value::format_type(&row, 100);
|
||||||
|
ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Describe;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Describe {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
128
crates/nu-cli/src/commands/do_.rs
Normal file
128
crates/nu-cli/src/commands/do_.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::Block, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Do;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct DoArgs {
|
||||||
|
block: Block,
|
||||||
|
ignore_errors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Do {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"do"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("do")
|
||||||
|
.required("block", SyntaxShape::Block, "the block to run ")
|
||||||
|
.switch(
|
||||||
|
"ignore_errors",
|
||||||
|
"ignore errors as the block runs",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Runs a block, optionally ignoring errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
do_(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Run the block",
|
||||||
|
example: r#"do { echo hello }"#,
|
||||||
|
result: Some(vec![Value::from("hello")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the block and ignore errors",
|
||||||
|
example: r#"do -i { thisisnotarealcommand }"#,
|
||||||
|
result: Some(vec![Value::nothing()]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_(
|
||||||
|
raw_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||||
|
|
||||||
|
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||||
|
let scope = raw_args.call_info.scope.clone();
|
||||||
|
let (
|
||||||
|
DoArgs {
|
||||||
|
ignore_errors,
|
||||||
|
mut block,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = raw_args.process(®istry).await?;
|
||||||
|
|
||||||
|
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, &mut context, input, scope).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;
|
||||||
|
context.clear_errors();
|
||||||
|
Ok(futures::stream::iter(output).to_output_stream())
|
||||||
|
}
|
||||||
|
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.map(|x| x.to_output_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Do;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Do {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
@@ -22,12 +22,12 @@ impl WholeStreamCommand for Drop {
|
|||||||
Signature::build("drop").optional(
|
Signature::build("drop").optional(
|
||||||
"rows",
|
"rows",
|
||||||
SyntaxShape::Number,
|
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 {
|
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(
|
async fn run(
|
||||||
@@ -35,20 +35,7 @@ impl WholeStreamCommand for Drop {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let (DropArgs { rows }, input) = args.process(®istry).await?;
|
drop(args, registry).await
|
||||||
let mut v: Vec<_> = input.into_vec().await;
|
|
||||||
|
|
||||||
let rows_to_drop = if let Some(quantity) = rows {
|
|
||||||
*quantity as usize
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
for _ in 0..rows_to_drop {
|
|
||||||
v.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(futures::stream::iter(v).to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -70,14 +57,40 @@ impl WholeStreamCommand for Drop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DropArgs { rows }, input) = args.process(®istry).await?;
|
||||||
|
let v: Vec<_> = input.into_vec().await;
|
||||||
|
|
||||||
|
let rows_to_drop = if let Some(quantity) = rows {
|
||||||
|
*quantity as usize
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(if rows_to_drop == 0 {
|
||||||
|
futures::stream::iter(v).to_output_stream()
|
||||||
|
} else {
|
||||||
|
let k = if v.len() < rows_to_drop {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
v.len() - rows_to_drop
|
||||||
|
};
|
||||||
|
|
||||||
|
let iter = v.into_iter().take(k);
|
||||||
|
|
||||||
|
futures::stream::iter(iter).to_output_stream()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Drop;
|
use super::Drop;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Drop {})
|
Ok(test_examples(Drop {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ where
|
|||||||
let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
|
let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
|
||||||
UntaggedValue::Table(values)
|
UntaggedValue::Table(values)
|
||||||
}
|
}
|
||||||
.retag(tag)
|
.into_value(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DirInfo> for Value {
|
impl From<DirInfo> for Value {
|
||||||
@@ -352,17 +352,17 @@ impl From<DirInfo> for Value {
|
|||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"path".to_string(),
|
"path".to_string(),
|
||||||
UntaggedValue::path(d.path).retag(&d.tag),
|
UntaggedValue::path(d.path).into_value(&d.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"apparent".to_string(),
|
"apparent".to_string(),
|
||||||
UntaggedValue::bytes(d.size).retag(&d.tag),
|
UntaggedValue::filesize(d.size).into_value(&d.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"physical".to_string(),
|
"physical".to_string(),
|
||||||
UntaggedValue::bytes(d.blocks).retag(&d.tag),
|
UntaggedValue::filesize(d.blocks).into_value(&d.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
r.insert("directories".to_string(), value_from_vec(d.dirs, &d.tag));
|
r.insert("directories".to_string(), value_from_vec(d.dirs, &d.tag));
|
||||||
@@ -376,7 +376,7 @@ impl From<DirInfo> for Value {
|
|||||||
.map(move |e| UntaggedValue::Error(e).into_untagged_value())
|
.map(move |e| UntaggedValue::Error(e).into_untagged_value())
|
||||||
.collect::<Vec<Value>>(),
|
.collect::<Vec<Value>>(),
|
||||||
)
|
)
|
||||||
.retag(&d.tag);
|
.into_value(&d.tag);
|
||||||
|
|
||||||
r.insert("errors".to_string(), v);
|
r.insert("errors".to_string(), v);
|
||||||
}
|
}
|
||||||
@@ -394,41 +394,45 @@ impl From<FileInfo> for Value {
|
|||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"path".to_string(),
|
"path".to_string(),
|
||||||
UntaggedValue::path(f.path).retag(&f.tag),
|
UntaggedValue::path(f.path).into_value(&f.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"apparent".to_string(),
|
"apparent".to_string(),
|
||||||
UntaggedValue::bytes(f.size).retag(&f.tag),
|
UntaggedValue::filesize(f.size).into_value(&f.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
let b = f
|
let b = f
|
||||||
.blocks
|
.blocks
|
||||||
.map(UntaggedValue::bytes)
|
.map(UntaggedValue::filesize)
|
||||||
.unwrap_or_else(UntaggedValue::nothing)
|
.unwrap_or_else(UntaggedValue::nothing)
|
||||||
.retag(&f.tag);
|
.into_value(&f.tag);
|
||||||
|
|
||||||
r.insert("physical".to_string(), b);
|
r.insert("physical".to_string(), b);
|
||||||
|
|
||||||
r.insert(
|
r.insert(
|
||||||
"directories".to_string(),
|
"directories".to_string(),
|
||||||
UntaggedValue::nothing().retag(&f.tag),
|
UntaggedValue::nothing().into_value(&f.tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
r.insert("files".to_string(), UntaggedValue::nothing().retag(&f.tag));
|
r.insert(
|
||||||
|
"files".to_string(),
|
||||||
|
UntaggedValue::nothing().into_value(&f.tag),
|
||||||
|
);
|
||||||
|
|
||||||
UntaggedValue::row(r).retag(&f.tag)
|
UntaggedValue::row(r).into_value(&f.tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Du;
|
use super::Du;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Du {})
|
Ok(test_examples(Du {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
use crate::commands::classified::block::run_block;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
use futures::stream::once;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{
|
|
||||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
|
||||||
SyntaxShape, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Each;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct EachArgs {
|
|
||||||
block: Block,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Each {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"each"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("each").required(
|
|
||||||
"block",
|
|
||||||
SyntaxShape::Block,
|
|
||||||
"the block to run on each row",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Run a block on each row of the table."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
each(args, registry).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Echo the square of each integer",
|
|
||||||
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
|
|
||||||
result: Some(vec![
|
|
||||||
UntaggedValue::int(1).into(),
|
|
||||||
UntaggedValue::int(4).into(),
|
|
||||||
UntaggedValue::int(9).into(),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Echo the sum of each row",
|
|
||||||
example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
|
|
||||||
result: Some(vec![
|
|
||||||
UntaggedValue::int(3).into(),
|
|
||||||
UntaggedValue::int(7).into(),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
|
||||||
match &*head {
|
|
||||||
SpannedExpression {
|
|
||||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
|
||||||
..
|
|
||||||
} if s == "expanded-each" => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_row(
|
|
||||||
block: Arc<Block>,
|
|
||||||
scope: Arc<Scope>,
|
|
||||||
head: Arc<Box<SpannedExpression>>,
|
|
||||||
mut context: Arc<Context>,
|
|
||||||
input: Value,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let input_clone = input.clone();
|
|
||||||
let input_stream = if is_expanded_it_usage(&head) {
|
|
||||||
InputStream::empty()
|
|
||||||
} else {
|
|
||||||
once(async { Ok(input_clone) }).to_input_stream()
|
|
||||||
};
|
|
||||||
Ok(run_block(
|
|
||||||
&block,
|
|
||||||
Arc::make_mut(&mut context),
|
|
||||||
input_stream,
|
|
||||||
&input,
|
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn each(
|
|
||||||
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(Context::from_raw(&raw_args, ®istry));
|
|
||||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
|
||||||
let block = Arc::new(each_args.block);
|
|
||||||
Ok(input
|
|
||||||
.then(move |input| {
|
|
||||||
let block = block.clone();
|
|
||||||
let scope = scope.clone();
|
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
|
||||||
async {
|
|
||||||
match process_row(block, scope, head, context, input).await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Each;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Each {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
175
crates/nu-cli/src/commands/each/command.rs
Normal file
175
crates/nu-cli/src/commands/each/command.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use futures::stream::once;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
||||||
|
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Each;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct EachArgs {
|
||||||
|
block: Block,
|
||||||
|
numbered: Tagged<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Each {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"each"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("each")
|
||||||
|
.required("block", SyntaxShape::Block, "the block to run on each row")
|
||||||
|
.switch(
|
||||||
|
"numbered",
|
||||||
|
"returned a numbered item ($it.index and $it.item)",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Run a block on each row of the table."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
each(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Echo the sum of each row",
|
||||||
|
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Echo the square of each integer",
|
||||||
|
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(1).into(),
|
||||||
|
UntaggedValue::int(4).into(),
|
||||||
|
UntaggedValue::int(9).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Number each item and echo a message",
|
||||||
|
example:
|
||||||
|
"echo ['bob' 'fred'] | each --numbered { echo `{{$it.index}} is {{$it.item}}` }",
|
||||||
|
result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||||
|
matches!(&*head, SpannedExpression {
|
||||||
|
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||||
|
..
|
||||||
|
} if s == "expanded-each")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_row(
|
||||||
|
block: Arc<Block>,
|
||||||
|
scope: Arc<Scope>,
|
||||||
|
head: Arc<Box<SpannedExpression>>,
|
||||||
|
mut context: Arc<EvaluationContext>,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let input_clone = input.clone();
|
||||||
|
let input_stream = if is_expanded_it_usage(&head) {
|
||||||
|
InputStream::empty()
|
||||||
|
} else {
|
||||||
|
once(async { Ok(input_clone) }).to_input_stream()
|
||||||
|
};
|
||||||
|
Ok(run_block(
|
||||||
|
&block,
|
||||||
|
Arc::make_mut(&mut context),
|
||||||
|
input_stream,
|
||||||
|
Scope::append_it(scope, input),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.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,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||||
|
let scope = raw_args.call_info.scope.clone();
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
|
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||||
|
let block = Arc::new(each_args.block);
|
||||||
|
|
||||||
|
if each_args.numbered.item {
|
||||||
|
Ok(input
|
||||||
|
.enumerate()
|
||||||
|
.then(move |input| {
|
||||||
|
let block = block.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let head = head.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let row = make_indexed_item(input.0, input.1);
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(block, scope, head, context, row).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
} else {
|
||||||
|
Ok(input
|
||||||
|
.then(move |input| {
|
||||||
|
let block = block.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let head = head.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(block, scope, head, context, input).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Each;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Each {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
134
crates/nu-cli/src/commands/each/group.rs
Normal file
134
crates/nu-cli/src/commands/each/group.rs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
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 = 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;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(EachGroup {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
crates/nu-cli/src/commands/each/mod.rs
Normal file
9
crates/nu-cli/src/commands/each/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
pub mod command;
|
||||||
|
pub mod group;
|
||||||
|
pub mod window;
|
||||||
|
|
||||||
|
pub(crate) use command::make_indexed_item;
|
||||||
|
pub use command::process_row;
|
||||||
|
pub use command::Each;
|
||||||
|
pub use group::EachGroup;
|
||||||
|
pub use window::EachWindow;
|
||||||
114
crates/nu-cli/src/commands/each/window.rs
Normal file
114
crates/nu-cli/src/commands/each/window.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
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 = 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;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(EachWindow {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ use crate::prelude::*;
|
|||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::Operator;
|
use nu_protocol::hir::Operator;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
Primitive, Range, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Echo;
|
pub struct Echo;
|
||||||
@@ -32,7 +32,7 @@ impl WholeStreamCommand for Echo {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
echo(args, registry)
|
echo(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -51,77 +51,123 @@ impl WholeStreamCommand for Echo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
|
||||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
for i in args.rest {
|
let stream = args.rest.into_iter().map(|i| match i.as_string() {
|
||||||
match i.as_string() {
|
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
Ok(s) => {
|
|
||||||
yield Ok(ReturnSuccess::Value(
|
|
||||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||||
));
|
))),
|
||||||
}
|
|
||||||
_ => match i {
|
_ => match i {
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Table(table),
|
value: UntaggedValue::Table(table),
|
||||||
..
|
..
|
||||||
} => {
|
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
|
||||||
for value in table {
|
.to_output_stream(),
|
||||||
yield Ok(ReturnSuccess::Value(value.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||||
tag
|
tag,
|
||||||
} => {
|
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
|
||||||
let mut current = range.from.0.item;
|
_ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
|
||||||
while current != range.to.0.item {
|
},
|
||||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
});
|
||||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
|
||||||
|
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RangeIterator {
|
||||||
|
curr: Primitive,
|
||||||
|
end: Primitive,
|
||||||
|
tag: Tag,
|
||||||
|
is_end_inclusive: 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: start,
|
||||||
|
end: range.to.0.item,
|
||||||
|
tag,
|
||||||
|
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for RangeIterator {
|
||||||
|
type Item = Result<ReturnSuccess, ShellError>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let ordering = if self.end == Primitive::Nothing {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
let result =
|
||||||
|
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"Cannot create range",
|
||||||
|
"unsupported range",
|
||||||
|
self.tag.span,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(result) = result {
|
||||||
|
return Some(Err(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = result
|
||||||
|
.expect("Internal error: the error case was already protected, but that failed");
|
||||||
|
|
||||||
|
result.compare()
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
|
||||||
|
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||||
|
|
||||||
|
let 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 {
|
Ok(result) => match result {
|
||||||
UntaggedValue::Primitive(p) => p,
|
UntaggedValue::Primitive(p) => p,
|
||||||
_ => {
|
_ => {
|
||||||
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment"));
|
return Some(Err(ShellError::unimplemented(
|
||||||
return;
|
"Internal error: expected a primitive result from increment",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err((left_type, right_type)) => {
|
Err((left_type, right_type)) => {
|
||||||
yield Err(ShellError::coerce_error(
|
return Some(Err(ShellError::coerce_error(
|
||||||
left_type.spanned(tag.span),
|
left_type.spanned(self.tag.span),
|
||||||
right_type.spanned(tag.span),
|
right_type.spanned(self.tag.span),
|
||||||
));
|
)));
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match range.to.1 {
|
|
||||||
RangeInclusion::Inclusive => {
|
|
||||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
yield Ok(ReturnSuccess::Value(i.clone()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Some(ReturnSuccess::value(output))
|
||||||
Ok(stream.to_output_stream())
|
} else {
|
||||||
|
// TODO: add inclusive/exclusive ranges
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Echo;
|
use super::Echo;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Echo {})
|
Ok(test_examples(Echo {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
288
crates/nu-cli/src/commands/empty.rs
Normal file
288
crates/nu-cli/src/commands/empty.rs
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::Block, ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use nu_value_ext::{as_string, ValueExt};
|
||||||
|
|
||||||
|
use futures::stream::once;
|
||||||
|
use indexmap::indexmap;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
rest: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"empty?"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("empty?").rest(
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the names of the columns to check emptiness. Pass an optional block to replace if empty",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check for empty values"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
is_empty(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Check if a value is empty",
|
||||||
|
example: "echo '' | empty?",
|
||||||
|
result: Some(vec![UntaggedValue::boolean(true).into()]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "more than one column",
|
||||||
|
example: "echo [[meal size]; [arepa small] [taco '']] | empty? meal size",
|
||||||
|
result: Some(
|
||||||
|
vec![
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"meal".to_string() => Value::from(false),
|
||||||
|
"size".to_string() => Value::from(false),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"meal".to_string() => Value::from(false),
|
||||||
|
"size".to_string() => Value::from(true),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},Example {
|
||||||
|
description: "use a block if setting the empty cell contents is wanted",
|
||||||
|
example: "echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] }",
|
||||||
|
result: Some(
|
||||||
|
vec![
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"2020/04/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(33).into(), UntaggedValue::int(37).into()]).into(),
|
||||||
|
"2020/07/10".to_string() => UntaggedValue::table(&[UntaggedValue::int(27).into()]).into(),
|
||||||
|
"2020/11/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(37).into()]).into(),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_empty(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let name_tag = Arc::new(args.call_info.name_tag.clone());
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||||
|
let scope = args.call_info.scope.clone();
|
||||||
|
let (Arguments { rest }, input) = args.process(®istry).await?;
|
||||||
|
let (columns, default_block): (Vec<ColumnPath>, Option<Block>) = arguments(rest)?;
|
||||||
|
let default_block = Arc::new(default_block);
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
let stream = futures::stream::iter(vec![
|
||||||
|
UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Ok(InputStream::from_stream(stream)
|
||||||
|
.then(move |input| {
|
||||||
|
let tag = name_tag.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = vec![];
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(scope, context, input, block, columns, tag).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.then(move |input| {
|
||||||
|
let tag = name_tag.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = columns.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(scope, context, input, block, columns, tag).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arguments(rest: Vec<Value>) -> Result<(Vec<ColumnPath>, Option<Block>), ShellError> {
|
||||||
|
let mut rest = rest;
|
||||||
|
let mut columns = vec![];
|
||||||
|
let mut default = None;
|
||||||
|
|
||||||
|
let last_argument = rest.pop();
|
||||||
|
|
||||||
|
match last_argument {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Block(call),
|
||||||
|
..
|
||||||
|
}) => default = Some(call),
|
||||||
|
Some(other) => {
|
||||||
|
let Tagged { item: path, .. } = other.as_column_path()?;
|
||||||
|
|
||||||
|
columns = vec![path];
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
for argument in rest {
|
||||||
|
let Tagged { item: path, .. } = argument.as_column_path()?;
|
||||||
|
|
||||||
|
columns.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((columns, default))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_row(
|
||||||
|
scope: Arc<Scope>,
|
||||||
|
mut context: Arc<EvaluationContext>,
|
||||||
|
input: Value,
|
||||||
|
default_block: Arc<Option<Block>>,
|
||||||
|
column_paths: Vec<ColumnPath>,
|
||||||
|
tag: Arc<Tag>,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let _tag = &*tag;
|
||||||
|
let mut out = Arc::new(None);
|
||||||
|
let results = Arc::make_mut(&mut out);
|
||||||
|
|
||||||
|
if let Some(default_block) = &*default_block {
|
||||||
|
let for_block = input.clone();
|
||||||
|
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||||
|
|
||||||
|
let scope = Scope::append_it(scope, input.clone());
|
||||||
|
|
||||||
|
let mut stream = run_block(
|
||||||
|
&default_block,
|
||||||
|
Arc::make_mut(&mut context),
|
||||||
|
input_stream,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*results = Some({
|
||||||
|
let values = stream.drain_vec().await;
|
||||||
|
|
||||||
|
let errors = context.get_errors();
|
||||||
|
|
||||||
|
if let Some(error) = errors.first() {
|
||||||
|
return Err(error.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if values.len() == 1 {
|
||||||
|
let value = values
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| ShellError::unexpected("No value."))?;
|
||||||
|
|
||||||
|
Value {
|
||||||
|
value: value.value.clone(),
|
||||||
|
tag: input.tag.clone(),
|
||||||
|
}
|
||||||
|
} else if values.is_empty() {
|
||||||
|
UntaggedValue::nothing().into_value(&input.tag)
|
||||||
|
} else {
|
||||||
|
UntaggedValue::table(&values).into_value(&input.tag)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match input {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Row(ref r),
|
||||||
|
ref tag,
|
||||||
|
} => {
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value({
|
||||||
|
let is_empty = input.is_empty();
|
||||||
|
|
||||||
|
if default_block.is_some() {
|
||||||
|
if is_empty {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||||
|
} else {
|
||||||
|
input.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
let mut obj = input.clone();
|
||||||
|
|
||||||
|
for column in column_paths.clone() {
|
||||||
|
let path = UntaggedValue::Primitive(Primitive::ColumnPath(column.clone()))
|
||||||
|
.into_value(tag);
|
||||||
|
let data = r.get_data(&as_string(&path)?).borrow().clone();
|
||||||
|
let is_empty = data.is_empty();
|
||||||
|
|
||||||
|
let default = if default_block.is_some() {
|
||||||
|
if is_empty {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||||
|
} else {
|
||||||
|
data.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(value) =
|
||||||
|
obj.swap_data_by_column_path(&column, Box::new(move |_| Ok(default)))
|
||||||
|
{
|
||||||
|
obj = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(obj)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Ok(OutputStream::one(ReturnSuccess::value({
|
||||||
|
if other.is_empty() {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(other.tag))
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(false).into_value(other.tag)
|
||||||
|
}
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::UnevaluatedCallInfo;
|
use crate::commands::UnevaluatedCallInfo;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::hir::ExternalRedirection;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
@@ -14,6 +15,7 @@ pub struct Enter;
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct EnterArgs {
|
pub struct EnterArgs {
|
||||||
location: Tagged<PathBuf>,
|
location: Tagged<PathBuf>,
|
||||||
|
encoding: Option<Tagged<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -23,15 +25,29 @@ impl WholeStreamCommand for Enter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("enter").required(
|
Signature::build("enter")
|
||||||
|
.required(
|
||||||
"location",
|
"location",
|
||||||
SyntaxShape::Path,
|
SyntaxShape::Path,
|
||||||
"the location to create a new shell from",
|
"the location to create a new shell from",
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"encoding",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"encoding to use to open file",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Create a new shell and begin at this path."
|
r#"Create a new shell and begin at this path.
|
||||||
|
|
||||||
|
Multiple encodings are supported for reading text files by using
|
||||||
|
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||||
|
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||||
|
|
||||||
|
For a more complete list of encodings please refer to the encoding_rs
|
||||||
|
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@@ -39,7 +55,7 @@ impl WholeStreamCommand for Enter {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
enter(args, registry)
|
enter(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -54,13 +70,20 @@ impl WholeStreamCommand for Enter {
|
|||||||
example: "enter package.json",
|
example: "enter package.json",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Enters file with iso-8859-1 encoding",
|
||||||
|
example: "enter file.csv --encoding iso-8859-1",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn enter(
|
||||||
|
raw_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
|
||||||
let scope = raw_args.call_info.scope.clone();
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let shell_manager = raw_args.shell_manager.clone();
|
let shell_manager = raw_args.shell_manager.clone();
|
||||||
let head = raw_args.call_info.args.head.clone();
|
let head = raw_args.call_info.args.head.clone();
|
||||||
@@ -68,7 +91,7 @@ fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
|||||||
let current_errors = raw_args.current_errors.clone();
|
let current_errors = raw_args.current_errors.clone();
|
||||||
let host = raw_args.host.clone();
|
let host = raw_args.host.clone();
|
||||||
let tag = raw_args.call_info.name_tag.clone();
|
let tag = raw_args.call_info.name_tag.clone();
|
||||||
let (EnterArgs { location }, _) = raw_args.process(®istry).await?;
|
let (EnterArgs { location, encoding }, _) = raw_args.process(®istry).await?;
|
||||||
let location_string = location.display().to_string();
|
let location_string = location.display().to_string();
|
||||||
let location_clone = location_string.clone();
|
let location_clone = location_string.clone();
|
||||||
|
|
||||||
@@ -79,41 +102,39 @@ fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
|||||||
let (_, command) = (spec[0], spec[1]);
|
let (_, command) = (spec[0], spec[1]);
|
||||||
|
|
||||||
if registry.has(command) {
|
if registry.has(command) {
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
|
CommandAction::EnterHelpShell(
|
||||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||||
|
),
|
||||||
)));
|
)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
UntaggedValue::nothing().into_value(Tag::unknown()),
|
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
|
||||||
)));
|
)))
|
||||||
} else if location.is_dir() {
|
} else if location.is_dir() {
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterShell(
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
location_clone,
|
CommandAction::EnterShell(location_clone),
|
||||||
)));
|
)))
|
||||||
} else {
|
} else {
|
||||||
// If it's a file, attempt to open the file as a value and enter it
|
// If it's a file, attempt to open the file as a value and enter it
|
||||||
let cwd = shell_manager.path();
|
let cwd = shell_manager.path();
|
||||||
|
|
||||||
let full_path = std::path::PathBuf::from(cwd);
|
let full_path = std::path::PathBuf::from(cwd);
|
||||||
|
|
||||||
let (file_extension, contents, contents_tag) =
|
let (file_extension, tagged_contents) = crate::commands::open::fetch(
|
||||||
crate::commands::open::fetch(
|
|
||||||
&full_path,
|
&full_path,
|
||||||
&PathBuf::from(location_clone),
|
&PathBuf::from(location_clone),
|
||||||
tag.span,
|
tag.span,
|
||||||
).await?;
|
encoding,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
match contents {
|
match tagged_contents.value {
|
||||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||||
let tagged_contents = contents.into_value(&contents_tag);
|
|
||||||
|
|
||||||
if let Some(extension) = file_extension {
|
if let Some(extension) = file_extension {
|
||||||
let command_name = format!("from {}", extension);
|
let command_name = format!("from {}", extension);
|
||||||
if let Some(converter) =
|
if let Some(converter) = registry.get_command(&command_name) {
|
||||||
registry.get_command(&command_name)
|
|
||||||
{
|
|
||||||
let new_args = RawCommandArgs {
|
let new_args = RawCommandArgs {
|
||||||
host,
|
host,
|
||||||
ctrl_c,
|
ctrl_c,
|
||||||
@@ -125,60 +146,57 @@ fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
|||||||
positional: None,
|
positional: None,
|
||||||
named: None,
|
named: None,
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
is_last: false,
|
external_redirection: ExternalRedirection::Stdout,
|
||||||
},
|
},
|
||||||
name_tag: tag.clone(),
|
name_tag: tag.clone(),
|
||||||
scope: scope.clone()
|
scope: scope.clone(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let mut result = converter.run(
|
let tag = tagged_contents.tag.clone();
|
||||||
new_args.with_input(vec![tagged_contents]),
|
let mut result = converter
|
||||||
®istry,
|
.run(new_args.with_input(vec![tagged_contents]), ®istry)
|
||||||
).await;
|
.await?;
|
||||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
result.drain_vec().await;
|
result.drain_vec().await;
|
||||||
for res in result_vec {
|
Ok(futures::stream::iter(result_vec.into_iter().map(
|
||||||
match res {
|
move |res| match res {
|
||||||
Ok(ReturnSuccess::Value(Value {
|
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
||||||
|
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
||||||
value,
|
value,
|
||||||
..
|
tag: tag.clone(),
|
||||||
})) => {
|
})),
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
),
|
||||||
Value {
|
x => x,
|
||||||
value,
|
},
|
||||||
tag: contents_tag.clone(),
|
))
|
||||||
})));
|
.to_output_stream())
|
||||||
}
|
} else {
|
||||||
x => yield x,
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
}
|
CommandAction::EnterValueShell(tagged_contents),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
}
|
CommandAction::EnterValueShell(tagged_contents),
|
||||||
} else {
|
)))
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
let tagged_contents = contents.into_value(contents_tag);
|
CommandAction::EnterValueShell(tagged_contents),
|
||||||
|
))),
|
||||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Enter;
|
use super::Enter;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Enter {})
|
Ok(test_examples(Enter {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::utils::data_processing::{evaluate, fetch};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
|
||||||
use nu_source::{SpannedItem, Tagged};
|
|
||||||
use nu_value_ext::ValueExt;
|
|
||||||
|
|
||||||
pub struct EvaluateBy;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct EvaluateByArgs {
|
|
||||||
evaluate_with: Option<Tagged<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for EvaluateBy {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"evaluate-by"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("evaluate-by").named(
|
|
||||||
"evaluate_with",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"the name of the column to evaluate by",
|
|
||||||
Some('w'),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Creates a new table with the data from the tables rows evaluated by the column given."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
evaluate_by(args, registry).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn evaluate_by(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let name = args.call_info.name_tag.clone();
|
|
||||||
let (EvaluateByArgs { evaluate_with }, mut input) = args.process(®istry).await?;
|
|
||||||
let values: Vec<Value> = input.collect().await;
|
|
||||||
|
|
||||||
if values.is_empty() {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"Expected table from pipeline",
|
|
||||||
"requires a table input",
|
|
||||||
name,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let evaluate_with = if let Some(evaluator) = evaluate_with {
|
|
||||||
Some(evaluator.item().clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match evaluate(&values[0], evaluate_with, name) {
|
|
||||||
Ok(evaluated) => Ok(OutputStream::one(ReturnSuccess::value(evaluated))),
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::EvaluateBy;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(EvaluateBy {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
104
crates/nu-cli/src/commands/every.rs
Normal file
104
crates/nu-cli/src/commands/every.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Every;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct EveryArgs {
|
||||||
|
stride: Tagged<u64>,
|
||||||
|
skip: Tagged<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Every {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"every"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required(
|
||||||
|
"stride",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"how many rows to skip between (and including) each row returned",
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"skip",
|
||||||
|
"skip the rows that would be returned, instead of selecting them",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show (or skip) every n-th row, starting from the first one."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
every(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get every second row",
|
||||||
|
example: "echo [1 2 3 4 5] | every 2",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(1).into(),
|
||||||
|
UntaggedValue::int(3).into(),
|
||||||
|
UntaggedValue::int(5).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Skip every second row",
|
||||||
|
example: "echo [1 2 3 4 5] | every 2 --skip",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(2).into(),
|
||||||
|
UntaggedValue::int(4).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let (EveryArgs { stride, skip }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let stride = stride.item;
|
||||||
|
let skip = skip.item;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(move |(i, value)| async move {
|
||||||
|
let stride_desired = if stride < 1 { 1 } else { stride } as usize;
|
||||||
|
let should_include = skip == (i % stride_desired != 0);
|
||||||
|
|
||||||
|
if should_include {
|
||||||
|
Some(ReturnSuccess::value(value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Every;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Every {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
crates/nu-cli/src/commands/exec.rs
Normal file
87
crates/nu-cli/src/commands/exec.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct Exec;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ExecArgs {
|
||||||
|
pub command: Tagged<PathBuf>,
|
||||||
|
pub rest: Vec<Tagged<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Exec {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"exec"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("exec")
|
||||||
|
.required("command", SyntaxShape::Path, "the command to execute")
|
||||||
|
.rest(SyntaxShape::Pattern, "any additional arguments for command")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Execute command"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
exec(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Execute 'ps aux'",
|
||||||
|
example: "exec ps aux",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Execute 'nautilus'",
|
||||||
|
example: "exec nautilus",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let registry = registry.clone();
|
||||||
|
let name = args.call_info.name_tag.clone();
|
||||||
|
let (args, _): (ExecArgs, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let mut command = Command::new(args.command.item);
|
||||||
|
for tagged_arg in args.rest {
|
||||||
|
command.arg(tagged_arg.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = command.exec(); // this replaces our process, should not return
|
||||||
|
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Error on exec",
|
||||||
|
format!("{}", err),
|
||||||
|
&name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Error on exec",
|
||||||
|
"exec is not supported on your platform",
|
||||||
|
&args.call_info.name_tag,
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::command::WholeStreamCommand;
|
use crate::commands::command::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||||
@@ -63,11 +63,12 @@ pub async fn exit(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Exit;
|
use super::Exit;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Exit {})
|
Ok(test_examples(Exit {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
@@ -72,11 +72,12 @@ async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::First;
|
use super::First;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(First {})
|
Ok(test_examples(First {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ impl WholeStreamCommand for Format {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
format_command(args, registry)
|
format_command(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -49,32 +49,43 @@ impl WholeStreamCommand for Format {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_command(
|
async fn format_command(
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = Arc::new(registry.clone());
|
||||||
let stream = async_stream! {
|
|
||||||
let scope = args.call_info.scope.clone();
|
let scope = args.call_info.scope.clone();
|
||||||
let (FormatArgs { pattern }, mut input) = args.process(®istry).await?;
|
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||||
let pattern_tag = pattern.tag.clone();
|
|
||||||
|
|
||||||
let format_pattern = format(&pattern);
|
let format_pattern = format(&pattern);
|
||||||
let commands = format_pattern;
|
let commands = Arc::new(format_pattern);
|
||||||
|
|
||||||
while let Some(value) = input.next().await {
|
Ok(input
|
||||||
|
.then(move |value| {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
let commands = commands.clone();
|
||||||
|
let registry = registry.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
|
||||||
for command in &commands {
|
async move {
|
||||||
|
for command in &*commands {
|
||||||
match command {
|
match command {
|
||||||
FormatCommand::Text(s) => {
|
FormatCommand::Text(s) => {
|
||||||
output.push_str(&s);
|
output.push_str(&s);
|
||||||
}
|
}
|
||||||
FormatCommand::Column(c) => {
|
FormatCommand::Column(c) => {
|
||||||
// FIXME: use the correct spans
|
// FIXME: use the correct spans
|
||||||
let full_column_path = nu_parser::parse_full_column_path(&(c.to_string()).spanned(Span::unknown()), ®istry);
|
let full_column_path = nu_parser::parse_full_column_path(
|
||||||
|
&(c.to_string()).spanned(Span::unknown()),
|
||||||
|
&*registry,
|
||||||
|
);
|
||||||
|
|
||||||
let result = evaluate_baseline_expr(&full_column_path.0, ®istry, &value, &scope.vars, &scope.env).await;
|
let result = evaluate_baseline_expr(
|
||||||
|
&full_column_path.0,
|
||||||
|
®istry,
|
||||||
|
Scope::append_it(scope.clone(), value.clone()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
if let Ok(c) = result {
|
if let Ok(c) = result {
|
||||||
output
|
output
|
||||||
@@ -86,12 +97,10 @@ fn format_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield ReturnSuccess::value(
|
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
|
||||||
UntaggedValue::string(output).into_untagged_value())
|
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.to_output_stream())
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -142,11 +151,12 @@ fn format(input: &str) -> Vec<FormatCommand> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Format;
|
use super::Format;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Format {})
|
Ok(test_examples(Format {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,25 +25,22 @@ impl WholeStreamCommand for From {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
yield Ok(ReturnSuccess::Value(
|
|
||||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||||
.into_value(Tag::unknown()),
|
.into_value(Tag::unknown()),
|
||||||
));
|
)))
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::From;
|
use super::From;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(From {})
|
Ok(test_examples(From {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,11 +109,12 @@ async fn from_csv(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromCSV;
|
use super::FromCSV;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromCSV {})
|
Ok(test_examples(FromCSV {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
|
|||||||
.delimiter(separator as u8)
|
.delimiter(separator as u8)
|
||||||
.from_reader(s.as_bytes());
|
.from_reader(s.as_bytes());
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
let headers = if headerless {
|
let headers = if headerless {
|
||||||
(1..=reader.headers()?.len())
|
(1..=reader.headers()?.len())
|
||||||
@@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
|
|||||||
if let Ok(i) = value.parse::<i64>() {
|
if let Ok(i) = value.parse::<i64>() {
|
||||||
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
||||||
} else if let Ok(f) = value.parse::<f64>() {
|
} else if let Ok(f) = value.parse::<f64>() {
|
||||||
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
|
tagged_row.insert_value(
|
||||||
|
header,
|
||||||
|
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,11 +130,12 @@ async fn from_eml(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromEML;
|
use super::FromEML;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromEML {})
|
Ok(test_examples(FromEML {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,15 @@ impl WholeStreamCommand for FromIcs {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
from_ics(args, registry)
|
from_ics(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn from_ics(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
|
||||||
let args = args.evaluate_once(®istry).await?;
|
let args = args.evaluate_once(®istry).await?;
|
||||||
let tag = args.name_tag();
|
let tag = args.name_tag();
|
||||||
let input = args.input;
|
let input = args.input;
|
||||||
@@ -44,19 +46,22 @@ fn from_ics(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
|
|||||||
let buf_reader = BufReader::new(input_bytes);
|
let buf_reader = BufReader::new(input_bytes);
|
||||||
let parser = ical::IcalParser::new(buf_reader);
|
let parser = ical::IcalParser::new(buf_reader);
|
||||||
|
|
||||||
|
// TODO: it should be possible to make this a stream, but the some of the lifetime requirements make this tricky.
|
||||||
|
// Pre-computing for now
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
for calendar in parser {
|
for calendar in parser {
|
||||||
match calendar {
|
match calendar {
|
||||||
Ok(c) => yield ReturnSuccess::value(calendar_to_value(c, tag.clone())),
|
Ok(c) => output.push(ReturnSuccess::value(calendar_to_value(c, tag.clone()))),
|
||||||
Err(_) => yield Err(ShellError::labeled_error(
|
Err(_) => output.push(Err(ShellError::labeled_error(
|
||||||
"Could not parse as .ics",
|
"Could not parse as .ics",
|
||||||
"input cannot be parsed as .ics",
|
"input cannot be parsed as .ics",
|
||||||
tag.clone()
|
tag.clone(),
|
||||||
)),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
Ok(futures::stream::iter(output).to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value {
|
fn calendar_to_value(calendar: IcalCalendar, tag: Tag) -> Value {
|
||||||
@@ -244,11 +249,12 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromIcs;
|
use super::FromIcs;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromIcs {})
|
Ok(test_examples(FromIcs {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,11 +95,12 @@ async fn from_ini(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromINI;
|
use super::FromINI;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromINI {})
|
Ok(test_examples(FromINI {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,17 +33,18 @@ impl WholeStreamCommand for FromJSON {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
from_json(args, registry)
|
from_json(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
|
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::String(s) => {
|
serde_hjson::Value::String(s) => {
|
||||||
@@ -71,75 +72,84 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson:
|
|||||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn from_json(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name_tag = args.call_info.name_tag.clone();
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let stream = async_stream! {
|
let (FromJSONArgs { objects }, input) = args.process(®istry).await?;
|
||||||
let (FromJSONArgs { objects }, mut input) = args.process(®istry).await?;
|
|
||||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||||
|
|
||||||
|
let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
|
||||||
|
|
||||||
if objects {
|
if objects {
|
||||||
for json_str in concat_string.item.lines() {
|
Ok(
|
||||||
|
futures::stream::iter(string_clone.into_iter().filter_map(move |json_str| {
|
||||||
if json_str.is_empty() {
|
if json_str.is_empty() {
|
||||||
continue;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match from_json_string_to_value(json_str.to_string(), &name_tag) {
|
match from_json_string_to_value(json_str, &name_tag) {
|
||||||
Ok(x) =>
|
Ok(x) => Some(ReturnSuccess::value(x)),
|
||||||
yield ReturnSuccess::value(x),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut message = "Could not parse as JSON (".to_string();
|
let mut message = "Could not parse as JSON (".to_string();
|
||||||
message.push_str(&e.to_string());
|
message.push_str(&e.to_string());
|
||||||
message.push_str(")");
|
message.push_str(")");
|
||||||
|
|
||||||
yield Err(ShellError::labeled_error_with_secondary(
|
Some(Err(ShellError::labeled_error_with_secondary(
|
||||||
message,
|
message,
|
||||||
"input cannot be parsed as JSON",
|
"input cannot be parsed as JSON",
|
||||||
&name_tag,
|
name_tag.clone(),
|
||||||
"value originates from here",
|
"value originates from here",
|
||||||
concat_string.tag.clone()))
|
concat_string.tag.clone(),
|
||||||
}
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
|
.to_output_stream(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
|
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
|
||||||
Ok(x) =>
|
Ok(x) => match x {
|
||||||
match x {
|
Value {
|
||||||
Value { value: UntaggedValue::Table(list), .. } => {
|
value: UntaggedValue::Table(list),
|
||||||
for l in list {
|
..
|
||||||
yield ReturnSuccess::value(l);
|
} => Ok(
|
||||||
}
|
futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||||
}
|
.to_output_stream(),
|
||||||
x => yield ReturnSuccess::value(x),
|
),
|
||||||
}
|
x => Ok(OutputStream::one(ReturnSuccess::value(x))),
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut message = "Could not parse as JSON (".to_string();
|
let mut message = "Could not parse as JSON (".to_string();
|
||||||
message.push_str(&e.to_string());
|
message.push_str(&e.to_string());
|
||||||
message.push_str(")");
|
message.push_str(")");
|
||||||
|
|
||||||
yield Err(ShellError::labeled_error_with_secondary(
|
Ok(OutputStream::one(Err(
|
||||||
|
ShellError::labeled_error_with_secondary(
|
||||||
message,
|
message,
|
||||||
"input cannot be parsed as JSON",
|
"input cannot be parsed as JSON",
|
||||||
name_tag,
|
name_tag,
|
||||||
"value originates from here",
|
"value originates from here",
|
||||||
concat_string.tag))
|
concat_string.tag,
|
||||||
|
),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromJSON;
|
use super::FromJSON;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromJSON {})
|
Ok(test_examples(FromJSON {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::TaggedListBuilder;
|
|
||||||
use calamine::*;
|
use calamine::*;
|
||||||
|
use nu_data::TaggedListBuilder;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
@@ -36,22 +36,29 @@ impl WholeStreamCommand for FromODS {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
from_ods(args, registry)
|
from_ods(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn from_ods(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let span = tag.span;
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let stream = async_stream! {
|
let (
|
||||||
let (FromODSArgs { headerless: _headerless }, mut input) = args.process(®istry).await?;
|
FromODSArgs {
|
||||||
|
headerless: _headerless,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
let bytes = input.collect_binary(tag.clone()).await?;
|
let bytes = input.collect_binary(tag.clone()).await?;
|
||||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
let buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
||||||
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error(
|
let mut ods = Ods::<_>::new(buf).map_err(|_| {
|
||||||
"Could not load ods file",
|
ShellError::labeled_error("Could not load ods file", "could not load ods file", &tag)
|
||||||
"could not load ods file",
|
})?;
|
||||||
&tag))?;
|
|
||||||
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
|
||||||
@@ -67,7 +74,7 @@ fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
|
|||||||
let value = match cell {
|
let value = match cell {
|
||||||
DataType::Empty => UntaggedValue::nothing(),
|
DataType::Empty => UntaggedValue::nothing(),
|
||||||
DataType::String(s) => UntaggedValue::string(s),
|
DataType::String(s) => UntaggedValue::string(s),
|
||||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||||
DataType::Int(i) => UntaggedValue::int(*i),
|
DataType::Int(i) => UntaggedValue::int(*i),
|
||||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||||
_ => UntaggedValue::nothing(),
|
_ => UntaggedValue::nothing(),
|
||||||
@@ -81,27 +88,26 @@ fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStrea
|
|||||||
|
|
||||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||||
} else {
|
} else {
|
||||||
yield Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Could not load sheet",
|
"Could not load sheet",
|
||||||
"could not load sheet",
|
"could not load sheet",
|
||||||
&tag));
|
&tag,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield ReturnSuccess::value(dict.into_value());
|
Ok(OutputStream::one(ReturnSuccess::value(dict.into_value())))
|
||||||
};
|
|
||||||
|
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FromODS;
|
use super::FromODS;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromODS {})
|
Ok(test_examples(FromODS {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl WholeStreamCommand for FromSSV {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
from_ssv(args, registry)
|
from_ssv(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ fn parse_aligned_columns<'a>(
|
|||||||
.flat_map(|s| find_indices(*s))
|
.flat_map(|s| find_indices(*s))
|
||||||
.collect::<Vec<usize>>();
|
.collect::<Vec<usize>>();
|
||||||
|
|
||||||
indices.sort();
|
indices.sort_unstable();
|
||||||
indices.dedup();
|
indices.dedup();
|
||||||
|
|
||||||
let headers: Vec<(String, usize)> = indices
|
let headers: Vec<(String, usize)> = indices
|
||||||
@@ -251,42 +251,60 @@ fn from_ssv_string_to_value(
|
|||||||
Some(UntaggedValue::Table(rows).into_value(&tag))
|
Some(UntaggedValue::Table(rows).into_value(&tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_ssv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn from_ssv(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let stream = async_stream! {
|
let (
|
||||||
let (FromSSVArgs { headerless, aligned_columns, minimum_spaces }, mut input) = args.process(®istry).await?;
|
FromSSVArgs {
|
||||||
|
headerless,
|
||||||
|
aligned_columns,
|
||||||
|
minimum_spaces,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
let concat_string = input.collect_string(name.clone()).await?;
|
let concat_string = input.collect_string(name.clone()).await?;
|
||||||
let split_at = match minimum_spaces {
|
let split_at = match minimum_spaces {
|
||||||
Some(number) => number.item,
|
Some(number) => number.item,
|
||||||
None => DEFAULT_MINIMUM_SPACES
|
None => DEFAULT_MINIMUM_SPACES,
|
||||||
};
|
};
|
||||||
|
|
||||||
match from_ssv_string_to_value(&concat_string.item, headerless, aligned_columns, split_at, name.clone()) {
|
Ok(
|
||||||
|
match from_ssv_string_to_value(
|
||||||
|
&concat_string.item,
|
||||||
|
headerless,
|
||||||
|
aligned_columns,
|
||||||
|
split_at,
|
||||||
|
name.clone(),
|
||||||
|
) {
|
||||||
Some(x) => match x {
|
Some(x) => match x {
|
||||||
Value { value: UntaggedValue::Table(list), ..} => {
|
Value {
|
||||||
for l in list { yield ReturnSuccess::value(l) }
|
value: UntaggedValue::Table(list),
|
||||||
}
|
..
|
||||||
x => yield ReturnSuccess::value(x)
|
} => futures::stream::iter(list.into_iter().map(ReturnSuccess::value))
|
||||||
|
.to_output_stream(),
|
||||||
|
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
yield Err(ShellError::labeled_error_with_secondary(
|
return Err(ShellError::labeled_error_with_secondary(
|
||||||
"Could not parse as SSV",
|
"Could not parse as SSV",
|
||||||
"input cannot be parsed ssv",
|
"input cannot be parsed ssv",
|
||||||
&name,
|
&name,
|
||||||
"value originates from here",
|
"value originates from here",
|
||||||
&concat_string.tag,
|
&concat_string.tag,
|
||||||
))
|
));
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
)
|
||||||
Ok(stream.to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn owned(x: &str, y: &str) -> (String, String) {
|
fn owned(x: &str, y: &str) -> (String, String) {
|
||||||
(String::from(x), String::from(y))
|
(String::from(x), String::from(y))
|
||||||
}
|
}
|
||||||
@@ -488,10 +506,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use super::FromSSV;
|
use super::FromSSV;
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(FromSSV {})
|
Ok(test_examples(FromSSV {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user