mirror of
https://github.com/nushell/nushell.git
synced 2025-08-20 03:08:34 +02:00
Compare commits
503 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d297199d7c | ||
|
|
17a433996e | ||
|
|
b9bb4692a4 | ||
|
|
d05dcdda02 | ||
|
|
27fe356214 | ||
|
|
fc44df1e45 | ||
|
|
77f915befe | ||
|
|
a5f7600f6f | ||
|
|
7eb8634ad7 | ||
|
|
452d8c06e9 | ||
|
|
48f535f02e | ||
|
|
43c10b0625 | ||
|
|
328b09fe04 | ||
|
|
15d49e4096 | ||
|
|
3ef53fe2cd | ||
|
|
7d8e759e98 | ||
|
|
69b3be61a4 | ||
|
|
79476a5cb2 | ||
|
|
f449baf8de | ||
|
|
5ff4bcfb7a | ||
|
|
98537ce8b7 | ||
|
|
d2a00a2daa | ||
|
|
f22938fc4a | ||
|
|
1e67ae8e94 | ||
|
|
c012d648fb | ||
|
|
67acaae53c | ||
|
|
e3da546e23 | ||
|
|
e5b136f70d | ||
|
|
058ef69da3 | ||
|
|
2a483531a4 | ||
|
|
05202671db | ||
|
|
8509873043 | ||
|
|
57a2d695e2 | ||
|
|
0b5ab1ef22 | ||
|
|
2eac79569c | ||
|
|
ac578b8491 | ||
|
|
5183fd25bb | ||
|
|
10f5a8ef78 | ||
|
|
a30837298d | ||
|
|
f377a3a7b4 | ||
|
|
83c874666a | ||
|
|
e000ed47cd | ||
|
|
af2f064f42 | ||
|
|
9c7b25134b | ||
|
|
2d15df9e6c | ||
|
|
d2ab287756 | ||
|
|
e73278990c | ||
|
|
12bc92df35 | ||
|
|
f19a801022 | ||
|
|
b193303aa3 | ||
|
|
e299e76fcf | ||
|
|
c857e18c4a | ||
|
|
5fb3df4054 | ||
|
|
8b597187fc | ||
|
|
930f9f0063 | ||
|
|
63d4df9810 | ||
|
|
13ba533fc4 | ||
|
|
6d60bab2fd | ||
|
|
5be774b2e5 | ||
|
|
b412ff92c0 | ||
|
|
5a75e11b0e | ||
|
|
e66bf70589 | ||
|
|
3924e9d50a | ||
|
|
8df748463d | ||
|
|
0113661c81 | ||
|
|
0ee054b14d | ||
|
|
80b39454ff | ||
|
|
97f3671e2c | ||
|
|
b674cee9d2 | ||
|
|
cb8491cfee | ||
|
|
8196b031f8 | ||
|
|
50dd56d3c4 | ||
|
|
0f7e1d4d01 | ||
|
|
ec77c572b9 | ||
|
|
f97561c416 | ||
|
|
5faa82e323 | ||
|
|
4e17292a12 | ||
|
|
666fbbb0d1 | ||
|
|
c6fe58467b | ||
|
|
46d1938f5c | ||
|
|
8229af7591 | ||
|
|
ee76523507 | ||
|
|
c283db373b | ||
|
|
1b0ed30516 | ||
|
|
a6fdee4a51 | ||
|
|
6951fb440c | ||
|
|
502c9ea706 | ||
|
|
22f67be461 | ||
|
|
77ffd06715 | ||
|
|
1d833ef972 | ||
|
|
0d8064ed2d | ||
|
|
cc06ea4d87 | ||
|
|
3cf7652e86 | ||
|
|
1eb28c6cb6 | ||
|
|
db590369a8 | ||
|
|
f4d654d2a2 | ||
|
|
5725e55abb | ||
|
|
b6d19cc9fa | ||
|
|
bc6c884a14 | ||
|
|
cb78bf8fd6 | ||
|
|
400bc97e35 | ||
|
|
2fd464bf7b | ||
|
|
e626522b3a | ||
|
|
791e07650d | ||
|
|
bf2363947b | ||
|
|
a2cc2259e7 | ||
|
|
808fe496a6 | ||
|
|
2fb48bd6ac | ||
|
|
2df8775b48 | ||
|
|
e02b4f1443 | ||
|
|
194782215f | ||
|
|
df17d28c0f | ||
|
|
5f43c8f024 | ||
|
|
1a18734f9a | ||
|
|
4a70c1ff4f | ||
|
|
770e5d89f2 | ||
|
|
cfac8e84dd | ||
|
|
43d90c1745 | ||
|
|
38bdb053d2 | ||
|
|
95e61773a5 | ||
|
|
4e931fa73f | ||
|
|
2573441e28 | ||
|
|
5770b15270 | ||
|
|
6817b472d0 | ||
|
|
2b076369e0 | ||
|
|
973a8ee8f3 | ||
|
|
1159d3365a | ||
|
|
152ba32eb7 | ||
|
|
153320ef33 | ||
|
|
ff236da72c | ||
|
|
54326869e4 | ||
|
|
f14f4e39c5 | ||
|
|
a18b2702ca | ||
|
|
93410c470e | ||
|
|
5d945ef869 | ||
|
|
df07be6a42 | ||
|
|
3c32d4947c | ||
|
|
2ea5235aea | ||
|
|
c096f031ce | ||
|
|
ae1d4bdb4c | ||
|
|
0adf2accdd | ||
|
|
4201f48be5 | ||
|
|
b076e375ca | ||
|
|
2f1016d44f | ||
|
|
ddf9d61346 | ||
|
|
f0b7ab5ecc | ||
|
|
e4c6336bd4 | ||
|
|
66061192f8 | ||
|
|
b7bc4c1f80 | ||
|
|
a56abb6502 | ||
|
|
892a416211 | ||
|
|
f45adecd01 | ||
|
|
bd015e82dc | ||
|
|
cf43b74f26 | ||
|
|
18909ec14a | ||
|
|
ed243c88d2 | ||
|
|
cb7723f423 | ||
|
|
9dc88f8a95 | ||
|
|
0a439fe52f | ||
|
|
a8b65e35ec | ||
|
|
bd9e598bf0 | ||
|
|
75f8247af1 | ||
|
|
e8ec5027ff | ||
|
|
ebba89ea31 | ||
|
|
8388afc9d9 | ||
|
|
b133724b38 | ||
|
|
09429d08aa | ||
|
|
9b577b8679 | ||
|
|
7a595827f1 | ||
|
|
332e12ded0 | ||
|
|
a508e15efe | ||
|
|
a5b6bb6209 | ||
|
|
1882a32b83 | ||
|
|
798766b4b5 | ||
|
|
193c4cc6d5 | ||
|
|
422b6ca871 | ||
|
|
2b13ac3856 | ||
|
|
4c10351579 | ||
|
|
dd27aaef1b | ||
|
|
6eb4a0e87b | ||
|
|
15f3a545f0 | ||
|
|
365f76ad19 | ||
|
|
df2845a9b4 | ||
|
|
8453261211 | ||
|
|
1dc8f3300e | ||
|
|
10d4edc7af | ||
|
|
50cbf91bc5 | ||
|
|
d05f9b3b1e | ||
|
|
f5fad393d0 | ||
|
|
d19a5f4c2f | ||
|
|
04451af776 | ||
|
|
232aca76a4 | ||
|
|
0178b53289 | ||
|
|
e05e6b42fe | ||
|
|
dd79afb503 | ||
|
|
599bb9797d | ||
|
|
c355585112 | ||
|
|
45f32c9541 | ||
|
|
7528094e12 | ||
|
|
dcfa135ab9 | ||
|
|
e9bb4f25eb | ||
|
|
0f7a9bbd31 | ||
|
|
73e65df5f6 | ||
|
|
a63a5adafa | ||
|
|
2eb4f8d28a | ||
|
|
d9ae66791a | ||
|
|
2c5939dc7d | ||
|
|
3150e70fc7 | ||
|
|
c9ffd6afc0 | ||
|
|
986b427038 | ||
|
|
c973850571 | ||
|
|
5a725f9651 | ||
|
|
79cc725aff | ||
|
|
e2cbc4e853 | ||
|
|
b5a27f0ccb | ||
|
|
bdb12f4bff | ||
|
|
c9c29f9e4c | ||
|
|
32951f1161 | ||
|
|
56f85b3108 | ||
|
|
16f85f32a2 | ||
|
|
2ae2f2ea9d | ||
|
|
4696c9069b | ||
|
|
1ffbb66e64 | ||
|
|
8dc7b8a7cd | ||
|
|
666e6a7b57 | ||
|
|
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 |
@@ -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
|
||||||
|
|||||||
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,8 +23,25 @@ A clear and concise description of what you expected to happen.
|
|||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Configuration (please complete the following information):**
|
**Configuration (please complete the following information):**
|
||||||
- OS (e.g. Windows):
|
|
||||||
- Nu version (you can use the `version` command to find out):
|
|
||||||
- Optional features (if any):
|
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Run `version | pivot` and paste the output to show OS, features, etc.
|
||||||
|
|
||||||
|
```
|
||||||
|
> version | pivot
|
||||||
|
╭───┬────────────────────┬───────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ # │ Column0 │ Column1 │
|
||||||
|
├───┼────────────────────┼───────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 0 │ version │ 0.24.1 │
|
||||||
|
│ 1 │ build_os │ macos-x86_64 │
|
||||||
|
│ 2 │ rust_version │ rustc 1.48.0 │
|
||||||
|
│ 3 │ cargo_version │ cargo 1.48.0 │
|
||||||
|
│ 4 │ pkg_version │ 0.24.1 │
|
||||||
|
│ 5 │ build_time │ 2020-12-18 09:54:09 │
|
||||||
|
│ 6 │ build_rust_channel │ release │
|
||||||
|
│ 7 │ features │ ctrlc, default, directories, dirs, git, ichwh, ptree, rich-benchmark, │
|
||||||
|
│ │ │ rustyline, term, uuid, which, zip │
|
||||||
|
╰───┴────────────────────┴───────────────────────────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**Add any other context about the problem here.**
|
||||||
|
|||||||
122
.github/workflows/release.yml
vendored
122
.github/workflows/release.yml
vendored
@@ -11,31 +11,39 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install libxcb
|
- name: Install libxcb
|
||||||
run: sudo apt-get install libxcb-composite0-dev
|
run: sudo apt-get install libxcb-composite0-dev
|
||||||
|
|
||||||
- name: Set up cargo
|
- name: Set up cargo
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --release --all --features=stable
|
args: --release --all --features=extra
|
||||||
|
|
||||||
- name: Create output directory
|
- name: Create output directory
|
||||||
run: mkdir output
|
run: mkdir output
|
||||||
|
|
||||||
- name: Copy files to output
|
- name: Copy files to output
|
||||||
run: |
|
run: |
|
||||||
cp target/release/nu target/release/nu_plugin_* output/
|
cp target/release/nu target/release/nu_plugin_* output/
|
||||||
cp README.build.txt output/README.txt
|
cp README.build.txt output/README.txt
|
||||||
|
cp LICENSE output/LICENSE
|
||||||
rm output/*.d
|
rm output/*.d
|
||||||
rm output/nu_plugin_core_*
|
rm output/nu_plugin_core_*
|
||||||
rm output/nu_plugin_stable_*
|
rm output/nu_plugin_extra_*
|
||||||
|
|
||||||
# Note: If OpenSSL changes, this path will need to be updated
|
# Note: If OpenSSL changes, this path will need to be updated
|
||||||
- name: Copy OpenSSL to output
|
- name: Copy OpenSSL to output
|
||||||
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
@@ -48,26 +56,32 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up cargo
|
- name: Set up cargo
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --release --all --features=stable
|
args: --release --all --features=extra
|
||||||
|
|
||||||
- name: Create output directory
|
- name: Create output directory
|
||||||
run: mkdir output
|
run: mkdir output
|
||||||
|
|
||||||
- name: Copy files to output
|
- name: Copy files to output
|
||||||
run: |
|
run: |
|
||||||
cp target/release/nu target/release/nu_plugin_* output/
|
cp target/release/nu target/release/nu_plugin_* output/
|
||||||
cp README.build.txt output/README.txt
|
cp README.build.txt output/README.txt
|
||||||
|
cp LICENSE output/LICENSE
|
||||||
rm output/*.d
|
rm output/*.d
|
||||||
rm output/nu_plugin_core_*
|
rm output/nu_plugin_core_*
|
||||||
rm output/nu_plugin_stable_*
|
rm output/nu_plugin_extra_*
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
@@ -80,58 +94,66 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up cargo
|
- name: Set up cargo
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Add cargo-wix subcommand
|
- name: Add cargo-wix subcommand
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: install
|
command: install
|
||||||
args: cargo-wix
|
args: cargo-wix
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --release --all --features=stable
|
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
|
- name: Create msi with wix
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: wix
|
command: wix
|
||||||
args: --no-build --output target\wix\nushell-windows.msi
|
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
||||||
- name: Create zip of binaries
|
|
||||||
run: powershell Compress-Archive nushell-windows.zip
|
|
||||||
working-directory: ./target/release
|
|
||||||
- name: Upload installer
|
- name: Upload installer
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: windows-installer
|
name: windows-installer
|
||||||
path: target/wix/nushell-windows.msi
|
path: target\wix\nushell-windows.msi
|
||||||
|
|
||||||
- name: Upload zip
|
- name: Upload zip
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: windows-zip
|
name: windows-zip
|
||||||
path: target/release/nushell-windows.zip
|
path: output\*
|
||||||
- name: Create output directory
|
|
||||||
run: mkdir output
|
|
||||||
- name: Copy files to output
|
|
||||||
run: |
|
|
||||||
cp target\release\nu.exe output\
|
|
||||||
cp target\release\nu_plugin_*.exe output\
|
|
||||||
cp README.build.txt output\README.txt
|
|
||||||
rm output\nu_plugin_core_*.exe
|
|
||||||
rm output\nu_plugin_stable_*.exe
|
|
||||||
# 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
|
|
||||||
- name: Download Less Binary
|
|
||||||
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.1/less.exe" -OutFile "output\less.exe"
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: windows
|
|
||||||
path: output/*
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Publish Release
|
name: Publish Release
|
||||||
@@ -143,6 +165,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Determine Release Info
|
- name: Determine Release Info
|
||||||
id: info
|
id: info
|
||||||
env:
|
env:
|
||||||
@@ -158,6 +181,7 @@ jobs:
|
|||||||
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
||||||
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
||||||
echo "::set-output name=innerdir::nushell-${VERSION}"
|
echo "::set-output name=innerdir::nushell-${VERSION}"
|
||||||
|
|
||||||
- name: Create Release Draft
|
- name: Create Release Draft
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
@@ -167,19 +191,24 @@ jobs:
|
|||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: ${{ steps.info.outputs.version }} Release
|
release_name: ${{ steps.info.outputs.version }} Release
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
- name: Create Linux Directory
|
- name: Create Linux Directory
|
||||||
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
- name: Download Linux Artifacts
|
- name: Download Linux Artifacts
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: linux
|
name: linux
|
||||||
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
- name: Restore Linux File Modes
|
- name: Restore Linux File Modes
|
||||||
run: |
|
run: |
|
||||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
||||||
|
|
||||||
- name: Create Linux tarball
|
- name: Create Linux tarball
|
||||||
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
||||||
|
|
||||||
- name: Upload Linux Artifact
|
- name: Upload Linux Artifact
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -189,17 +218,22 @@ jobs:
|
|||||||
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||||
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||||
asset_content_type: application/gzip
|
asset_content_type: application/gzip
|
||||||
|
|
||||||
- name: Create macOS Directory
|
- name: Create macOS Directory
|
||||||
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
- name: Download macOS Artifacts
|
- name: Download macOS Artifacts
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
|
|
||||||
- name: Restore macOS File Modes
|
- name: Restore macOS File Modes
|
||||||
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||||
|
|
||||||
- name: Create macOS Archive
|
- name: Create macOS Archive
|
||||||
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
||||||
|
|
||||||
- name: Upload macOS Artifact
|
- name: Upload macOS Artifact
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -209,19 +243,23 @@ jobs:
|
|||||||
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
||||||
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
- name: Create Windows Directory
|
- name: Create Windows Directory
|
||||||
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
- name: Download Windows Artifacts
|
|
||||||
|
- name: Download Windows zip
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: windows
|
name: windows-zip
|
||||||
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
# TODO: Remove Show
|
|
||||||
- name: Show Windows Artifacts
|
- name: Show Windows Artifacts
|
||||||
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||||
- name: Create Windows Archive
|
|
||||||
|
- name: Create macOS Archive
|
||||||
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
||||||
- name: Upload Windows Artifact
|
|
||||||
|
- name: Upload Windows zip
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -230,3 +268,19 @@ jobs:
|
|||||||
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
||||||
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
||||||
asset_content_type: application/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
|
||||||
|
|||||||
6
.gitpod.Dockerfile
vendored
6
.gitpod.Dockerfile
vendored
@@ -1,5 +1,9 @@
|
|||||||
FROM gitpod/workspace-full
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
|
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
|
||||||
|
# To force a rebuild, simply increase this counter:
|
||||||
|
ENV TRIGGER_REBUILD 1
|
||||||
|
|
||||||
USER gitpod
|
USER gitpod
|
||||||
|
|
||||||
RUN sudo apt-get update && \
|
RUN sudo apt-get update && \
|
||||||
@@ -11,4 +15,4 @@ RUN sudo apt-get update && \
|
|||||||
rust-lldb \
|
rust-lldb \
|
||||||
&& sudo rm -rf /var/lib/apt/lists/*
|
&& sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV RUST_LLDB=/usr/bin/lldb-8
|
ENV RUST_LLDB=/usr/bin/lldb-11
|
||||||
|
|||||||
@@ -25,38 +25,51 @@ 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
|
||||||
|
```
|
||||||
|
|||||||
4211
Cargo.lock
generated
4211
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
228
Cargo.toml
228
Cargo.toml
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nu"
|
|
||||||
version = "0.15.1"
|
|
||||||
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.25.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*/"]
|
members = ["crates/*/"]
|
||||||
@@ -18,67 +18,113 @@ 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.1", path = "./crates/nu-cli" }
|
nu-cli = {version = "0.25.0", path = "./crates/nu-cli"}
|
||||||
nu-source = { version = "0.15.1", path = "./crates/nu-source" }
|
nu-data = {version = "0.25.0", path = "./crates/nu-data"}
|
||||||
nu-plugin = { version = "0.15.1", path = "./crates/nu-plugin" }
|
nu-errors = {version = "0.25.0", path = "./crates/nu-errors"}
|
||||||
nu-protocol = { version = "0.15.1", path = "./crates/nu-protocol" }
|
nu-parser = {version = "0.25.0", path = "./crates/nu-parser"}
|
||||||
nu-errors = { version = "0.15.1", path = "./crates/nu-errors" }
|
nu-plugin = {version = "0.25.0", path = "./crates/nu-plugin"}
|
||||||
nu-parser = { version = "0.15.1", path = "./crates/nu-parser" }
|
nu-protocol = {version = "0.25.0", path = "./crates/nu-protocol"}
|
||||||
nu-value-ext = { version = "0.15.1", path = "./crates/nu-value-ext" }
|
nu-source = {version = "0.25.0", path = "./crates/nu-source"}
|
||||||
nu_plugin_binaryview = { version = "0.15.1", path = "./crates/nu_plugin_binaryview", optional=true }
|
nu-value-ext = {version = "0.25.0", path = "./crates/nu-value-ext"}
|
||||||
nu_plugin_fetch = { version = "0.15.1", path = "./crates/nu_plugin_fetch", optional=true }
|
|
||||||
nu_plugin_inc = { version = "0.15.1", path = "./crates/nu_plugin_inc", optional=true }
|
|
||||||
nu_plugin_match = { version = "0.15.1", path = "./crates/nu_plugin_match", optional=true }
|
|
||||||
nu_plugin_post = { version = "0.15.1", path = "./crates/nu_plugin_post", optional=true }
|
|
||||||
nu_plugin_ps = { version = "0.15.1", path = "./crates/nu_plugin_ps", optional=true }
|
|
||||||
nu_plugin_start = { version = "0.15.1", path = "./crates/nu_plugin_start", optional=true }
|
|
||||||
nu_plugin_sys = { version = "0.15.1", path = "./crates/nu_plugin_sys", optional=true }
|
|
||||||
nu_plugin_textview = { version = "0.15.1", path = "./crates/nu_plugin_textview", optional=true }
|
|
||||||
nu_plugin_tree = { version = "0.15.1", path = "./crates/nu_plugin_tree", optional=true }
|
|
||||||
|
|
||||||
crossterm = { version = "0.17.5", optional = true }
|
nu_plugin_binaryview = {version = "0.25.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||||
semver = { version = "0.10.0", optional = true }
|
nu_plugin_chart = {version = "0.25.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.25.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||||
url = { version = "2.1.1", optional = true }
|
nu_plugin_from_bson = {version = "0.25.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||||
|
nu_plugin_from_sqlite = {version = "0.25.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||||
|
nu_plugin_inc = {version = "0.25.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||||
|
nu_plugin_match = {version = "0.25.0", path = "./crates/nu_plugin_match", optional = true}
|
||||||
|
nu_plugin_post = {version = "0.25.0", path = "./crates/nu_plugin_post", optional = true}
|
||||||
|
nu_plugin_ps = {version = "0.25.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||||
|
nu_plugin_s3 = {version = "0.25.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||||
|
nu_plugin_start = {version = "0.25.0", path = "./crates/nu_plugin_start", optional = true}
|
||||||
|
nu_plugin_sys = {version = "0.25.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||||
|
nu_plugin_textview = {version = "0.25.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||||
|
nu_plugin_to_bson = {version = "0.25.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
|
nu_plugin_to_sqlite = {version = "0.25.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||||
|
nu_plugin_tree = {version = "0.25.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||||
|
nu_plugin_xpath = {version = "0.25.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||||
|
nu_plugin_selector = {version = "0.25.0", path = "./crates/nu_plugin_selector", 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"
|
||||||
starship = "0.42.0"
|
itertools = "0.9.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { version = "0.15.1", path = "./crates/nu-test-support" }
|
dunce = "1.0.1"
|
||||||
|
nu-test-support = {version = "0.25.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.1", path = "./crates/nu-build" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sys", "ps", "textview", "inc"]
|
ctrlc-support = ["nu-cli/ctrlc"]
|
||||||
stable = ["default", "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",
|
||||||
|
"zip-support"
|
||||||
|
]
|
||||||
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
|
||||||
|
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"]
|
||||||
|
zip-support = ["nu-cli/zip"]
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
selector = ["nu_plugin_selector"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
#strip = "symbols" #Couldn't get working +nightly
|
||||||
|
opt-level = 'z' #Optimize for size
|
||||||
|
lto = true #Link Time Optimization
|
||||||
|
codegen-units = 1 #Reduce parallel codegen units
|
||||||
|
|
||||||
# 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
|
||||||
@@ -103,37 +149,83 @@ 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"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_selector"
|
||||||
|
path = "src/plugins/nu_plugin_extra_selector.rs"
|
||||||
|
required-features = ["selector"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_from_bson"
|
||||||
|
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||||
|
required-features = ["bson"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_to_bson"
|
||||||
|
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||||
|
required-features = ["bson"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_from_sqlite"
|
||||||
|
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||||
|
required-features = ["sqlite"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_to_sqlite"
|
||||||
|
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||||
|
required-features = ["sqlite"]
|
||||||
|
|
||||||
# Main nu binary
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -5,8 +5,9 @@
|
|||||||
[](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
|
## Nushell
|
||||||
|
|
||||||
A new type of shell.
|
A new type of shell.
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
|
|||||||
|
|
||||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
||||||
|
|
||||||
You can also find more learning resources in our [documentation](https://www.nushell.sh/documentation.html) site.
|
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
Try it in Gitpod.
|
Try it in Gitpod.
|
||||||
|
|
||||||
@@ -43,19 +44,19 @@ Try it in Gitpod.
|
|||||||
|
|
||||||
### 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/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||||
|
|
||||||
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
|
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||||
|
|
||||||
Required dependencies:
|
Required dependencies:
|
||||||
|
|
||||||
* 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`):
|
||||||
|
|
||||||
@@ -63,10 +64,10 @@ To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs
|
|||||||
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/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --workspace --features=stable
|
cargo build --workspace --features=extra
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
@@ -193,7 +194,7 @@ For example, you can load a .toml file as structured data and explore it:
|
|||||||
> 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]
|
||||||
@@ -218,28 +219,28 @@ We can pipeline this into a command that gets the contents of one of the columns
|
|||||||
name │ nu
|
name │ nu
|
||||||
readme │ README.md
|
readme │ README.md
|
||||||
repository │ https://github.com/nushell/nushell
|
repository │ https://github.com/nushell/nushell
|
||||||
version │ 0.15.1
|
version │ 0.21.0
|
||||||
───────────────┴────────────────────────────────────
|
───────────────┴────────────────────────────────────
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we can use commands outside of Nu once we have the data we want:
|
Finally, we can use commands outside of Nu once we have the data we want:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version | echo $it
|
> open Cargo.toml | get package.version
|
||||||
0.15.1
|
0.21.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||||
|
|
||||||
### 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/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
|
```shell
|
||||||
> config --set [edit_mode "vi"]
|
> config set edit_mode "vi"
|
||||||
> config --set [path $nu.path]
|
> config set path $nu.path
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shells
|
### Shells
|
||||||
@@ -306,7 +307,7 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
|||||||
|
|
||||||
## Current Roadmap
|
## 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).
|
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
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
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.1"
|
|
||||||
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,115 +1,133 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nu-cli"
|
|
||||||
version = "0.15.1"
|
|
||||||
authors = ["The Nu Project Contributors"]
|
authors = ["The Nu Project Contributors"]
|
||||||
|
build = "build.rs"
|
||||||
description = "CLI for nushell"
|
description = "CLI for nushell"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
name = "nu-cli"
|
||||||
|
version = "0.25.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-source = { version = "0.15.1", path = "../nu-source" }
|
nu-data = {version = "0.25.0", path = "../nu-data"}
|
||||||
nu-plugin = { version = "0.15.1", path = "../nu-plugin" }
|
nu-errors = {version = "0.25.0", path = "../nu-errors"}
|
||||||
nu-protocol = { version = "0.15.1", path = "../nu-protocol" }
|
nu-json = {version = "0.25.0", path = "../nu-json"}
|
||||||
nu-errors = { version = "0.15.1", path = "../nu-errors" }
|
nu-parser = {version = "0.25.0", path = "../nu-parser"}
|
||||||
nu-parser = { version = "0.15.1", path = "../nu-parser" }
|
nu-plugin = {version = "0.25.0", path = "../nu-plugin"}
|
||||||
nu-value-ext = { version = "0.15.1", path = "../nu-value-ext" }
|
nu-protocol = {version = "0.25.0", path = "../nu-protocol"}
|
||||||
nu-test-support = { version = "0.15.1", path = "../nu-test-support" }
|
nu-source = {version = "0.25.0", path = "../nu-source"}
|
||||||
nu-table = {version = "0.15.1", path = "../nu-table"}
|
nu-stream = {version = "0.25.0", path = "../nu-stream"}
|
||||||
|
nu-table = {version = "0.25.0", path = "../nu-table"}
|
||||||
|
nu-test-support = {version = "0.25.0", path = "../nu-test-support"}
|
||||||
|
nu-value-ext = {version = "0.25.0", path = "../nu-value-ext"}
|
||||||
|
|
||||||
|
Inflector = "0.11"
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
app_dirs = "1.2.1"
|
arboard = {version = "1.1.0", optional = true}
|
||||||
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.13.0"
|
||||||
base64 = "0.12.1"
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
bigdecimal = { version = "0.1.2", features = ["serde"] }
|
byte-unit = "4.0.9"
|
||||||
bson = { version = "0.14.1", features = ["decimal128"] }
|
bytes = "0.5.6"
|
||||||
byte-unit = "3.1.3"
|
calamine = "0.16.1"
|
||||||
bytes = "0.5.4"
|
chrono = {version = "0.4.15", features = ["serde"]}
|
||||||
calamine = "0.16"
|
chrono-tz = "0.5.3"
|
||||||
cfg-if = "0.1"
|
clap = "2.33.3"
|
||||||
chrono = { version = "0.4.11", features = ["serde"] }
|
codespan-reporting = "0.9.5"
|
||||||
clap = "2.33.1"
|
csv = "1.1.3"
|
||||||
csv = "1.1"
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
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"
|
||||||
|
encoding_rs = "0.8.24"
|
||||||
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-rc.1", 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"
|
lazy_static = "1.*"
|
||||||
log = "0.4.8"
|
log = "0.4.11"
|
||||||
meval = "0.2"
|
meval = "0.2.0"
|
||||||
natural = "0.5.0"
|
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||||
num-traits = "0.2.11"
|
num-traits = "0.2.12"
|
||||||
parking_lot = "0.10.2"
|
parking_lot = "0.11.0"
|
||||||
pin-utils = "0.1.0"
|
pin-utils = "0.1.0"
|
||||||
pretty-hex = "0.1.1"
|
pretty-hex = "0.2.0"
|
||||||
pretty_env_logger = "0.4.0"
|
ptree = {version = "0.3.0", optional = true}
|
||||||
ptree = {version = "0.2" }
|
|
||||||
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"
|
rayon = "1.4.0"
|
||||||
rustyline = "6.2.0"
|
regex = "1.3.9"
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
roxmltree = "0.13.0"
|
||||||
serde-hjson = "0.9.1"
|
rust-embed = "5.6.0"
|
||||||
serde_bytes = "0.11.4"
|
rustyline = {version = "6.3.0", optional = true}
|
||||||
|
serde = {version = "1.0.115", features = ["derive"]}
|
||||||
|
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"]}
|
titlecase = "1.0"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
typetag = "0.1.4"
|
trash = {version = "1.2.0", optional = true}
|
||||||
umask = "1.0.0"
|
unicode-segmentation = "1.6.0"
|
||||||
unicode-xid = "0.2.0"
|
uom = {version = "0.30.0", features = ["f64", "try-from"]}
|
||||||
uuid_crate = { package = "uuid", version = "0.8.1", features = ["v4"] }
|
url = "2.1.1"
|
||||||
which = "4.0.1"
|
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||||
|
which = {version = "4.0.2", optional = true}
|
||||||
trash = { version = "1.0.1", optional = true }
|
zip = {version = "0.5.7", optional = true}
|
||||||
clipboard = { version = "0.5", optional = true }
|
|
||||||
starship = "0.42.0"
|
|
||||||
rayon = "1.3.0"
|
|
||||||
encoding_rs = "0.8.23"
|
|
||||||
|
|
||||||
[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.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
nu-build = { version = "0.15.1", path = "../nu-build" }
|
shadow-rs = "0.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9.2"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
clipboard-cli = ["arboard"]
|
||||||
|
rich-benchmark = ["heim"]
|
||||||
|
rustyline-support = ["rustyline"]
|
||||||
stable = []
|
stable = []
|
||||||
# starship-prompt = ["starship"]
|
|
||||||
clipboard-cli = ["clipboard"]
|
|
||||||
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.
3
crates/nu-cli/build.rs
Normal file
3
crates/nu-cli/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() -> shadow_rs::SdResult<()> {
|
||||||
|
shadow_rs::new()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,45 +4,54 @@ pub(crate) mod macros;
|
|||||||
mod from_delimited_data;
|
mod from_delimited_data;
|
||||||
mod to_delimited_data;
|
mod to_delimited_data;
|
||||||
|
|
||||||
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 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 def;
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
|
pub(crate) mod default_context;
|
||||||
|
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 evaluate_by;
|
|
||||||
pub(crate) mod every;
|
pub(crate) mod every;
|
||||||
|
pub(crate) mod exec;
|
||||||
pub(crate) mod exit;
|
pub(crate) mod exit;
|
||||||
pub(crate) mod first;
|
pub(crate) mod first;
|
||||||
|
pub(crate) mod flatten;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
pub(crate) mod from;
|
pub(crate) mod from;
|
||||||
pub(crate) mod from_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;
|
||||||
@@ -54,75 +63,72 @@ pub(crate) mod from_yaml;
|
|||||||
pub(crate) mod get;
|
pub(crate) mod get;
|
||||||
pub(crate) mod group_by;
|
pub(crate) mod group_by;
|
||||||
pub(crate) mod group_by_date;
|
pub(crate) mod group_by_date;
|
||||||
|
pub(crate) mod hash_;
|
||||||
pub(crate) mod headers;
|
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 let_;
|
||||||
|
pub(crate) mod let_env;
|
||||||
pub(crate) mod lines;
|
pub(crate) mod lines;
|
||||||
pub(crate) mod ls;
|
pub(crate) mod ls;
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) mod map_max_by;
|
|
||||||
pub(crate) mod math;
|
pub(crate) mod math;
|
||||||
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 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;
|
||||||
pub(crate) mod rm;
|
pub(crate) mod rm;
|
||||||
pub(crate) mod run_alias;
|
|
||||||
pub(crate) mod run_external;
|
pub(crate) mod run_external;
|
||||||
pub(crate) mod save;
|
pub(crate) mod save;
|
||||||
pub(crate) mod select;
|
pub(crate) mod select;
|
||||||
|
pub(crate) mod seq;
|
||||||
|
pub(crate) mod seq_dates;
|
||||||
pub(crate) mod shells;
|
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 source;
|
||||||
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_;
|
||||||
#[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,46 +140,57 @@ pub(crate) use command::{
|
|||||||
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
|
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use alias::Alias;
|
pub(crate) use ansi::Ansi;
|
||||||
pub(crate) use append::Append;
|
pub(crate) use append::Command as Append;
|
||||||
|
pub(crate) use autoenv::Autoenv;
|
||||||
|
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||||
|
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||||
|
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, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||||
pub(crate) use debug::Debug;
|
pub(crate) use debug::Debug;
|
||||||
|
pub(crate) use def::Def;
|
||||||
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 evaluate_by::EvaluateBy;
|
|
||||||
pub(crate) use every::Every;
|
pub(crate) use every::Every;
|
||||||
|
pub(crate) use exec::Exec;
|
||||||
pub(crate) use exit::Exit;
|
pub(crate) use exit::Exit;
|
||||||
pub(crate) use first::First;
|
pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use flatten::Command as Flatten;
|
||||||
|
pub(crate) use format::{FileSize, Format};
|
||||||
pub(crate) use from::From;
|
pub(crate) use from::From;
|
||||||
pub(crate) use from_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;
|
||||||
@@ -184,39 +201,47 @@ 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 hash_::{Hash, HashBase64};
|
||||||
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 let_::Let;
|
||||||
|
pub(crate) use let_env::LetEnv;
|
||||||
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 map_max_by::MapMaxBy;
|
|
||||||
pub(crate) use math::{
|
pub(crate) use math::{
|
||||||
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
|
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
|
||||||
|
MathMinimum, MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||||
};
|
};
|
||||||
pub(crate) use merge::Merge;
|
pub(crate) use merge::Merge;
|
||||||
pub(crate) use mkdir::Mkdir;
|
pub(crate) use mkdir::Mkdir;
|
||||||
pub(crate) use mv::Move;
|
pub(crate) use move_::{Move, Mv};
|
||||||
pub(crate) use next::Next;
|
pub(crate) use next::Next;
|
||||||
pub(crate) use nth::Nth;
|
pub(crate) use nth::Nth;
|
||||||
pub(crate) use open::Open;
|
pub(crate) use open::Open;
|
||||||
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;
|
||||||
pub(crate) use random::{Random, RandomUUID};
|
#[cfg(feature = "uuid_crate")]
|
||||||
|
pub(crate) use random::RandomUUID;
|
||||||
|
pub(crate) use random::{
|
||||||
|
Random, RandomBool, RandomChars, RandomDecimal, 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;
|
||||||
@@ -224,43 +249,83 @@ pub(crate) use rm::Remove;
|
|||||||
pub(crate) use run_external::RunExternalCommand;
|
pub(crate) use run_external::RunExternalCommand;
|
||||||
pub(crate) use save::Save;
|
pub(crate) use save::Save;
|
||||||
pub(crate) use select::Select;
|
pub(crate) use select::Select;
|
||||||
|
pub(crate) use seq::Seq;
|
||||||
|
pub(crate) use seq_dates::SeqDates;
|
||||||
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 source::Source;
|
||||||
pub(crate) use split::SplitColumn;
|
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||||
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,
|
||||||
};
|
};
|
||||||
#[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 full_tests() -> Vec<Command> {
|
||||||
|
vec![
|
||||||
|
whole_stream_command(Append),
|
||||||
|
whole_stream_command(GroupBy),
|
||||||
|
whole_stream_command(Insert),
|
||||||
|
whole_stream_command(Move),
|
||||||
|
whole_stream_command(Update),
|
||||||
|
whole_stream_command(Empty),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn only_examples() -> Vec<Command> {
|
||||||
|
let mut commands = full_tests();
|
||||||
|
commands.extend(vec![whole_stream_command(Flatten)]);
|
||||||
|
commands
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
for cmd in only_examples() {
|
||||||
|
test_examples(cmd)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tracks_metadata() -> Result<(), ShellError> {
|
||||||
|
for cmd in full_tests() {
|
||||||
|
test_anchors(cmd)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::data::config;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{
|
|
||||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tagged;
|
|
||||||
|
|
||||||
pub struct Alias;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct AliasArgs {
|
|
||||||
pub name: Tagged<String>,
|
|
||||||
pub args: Vec<Value>,
|
|
||||||
pub block: Block,
|
|
||||||
pub save: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Alias {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"alias"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("alias")
|
|
||||||
.required("name", SyntaxShape::String, "the name of the alias")
|
|
||||||
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
|
||||||
.required(
|
|
||||||
"block",
|
|
||||||
SyntaxShape::Block,
|
|
||||||
"the block to run as the body of the alias",
|
|
||||||
)
|
|
||||||
.switch("save", "save the alias to your config", Some('s'))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Define a shortcut for another command."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
alias(args, registry).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "An alias without parameters",
|
|
||||||
example: "alias say-hi [] { echo 'Hello!' }",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "An alias with a single parameter",
|
|
||||||
example: "alias l [x] { ls $x }",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn alias(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let mut raw_input = args.raw_input.clone();
|
|
||||||
let (
|
|
||||||
AliasArgs {
|
|
||||||
name,
|
|
||||||
args: list,
|
|
||||||
block,
|
|
||||||
save,
|
|
||||||
},
|
|
||||||
_ctx,
|
|
||||||
) = args.process(®istry).await?;
|
|
||||||
let mut processed_args: Vec<String> = vec![];
|
|
||||||
|
|
||||||
if let Some(true) = save {
|
|
||||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
|
||||||
|
|
||||||
// process the alias to remove the --save flag
|
|
||||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
|
||||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
|
||||||
let left = raw_input[..left_brace]
|
|
||||||
.replace("--save", "")
|
|
||||||
.replace("-s", "");
|
|
||||||
let right = raw_input[right_brace..]
|
|
||||||
.replace("--save", "")
|
|
||||||
.replace("-s", "");
|
|
||||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
|
||||||
|
|
||||||
// create a value from raw_input alias
|
|
||||||
let alias: Value = raw_input.trim().to_string().into();
|
|
||||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
|
||||||
|
|
||||||
// add to startup if alias doesn't exist and replce if it does
|
|
||||||
match result.get_mut("startup") {
|
|
||||||
Some(startup) => {
|
|
||||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
|
||||||
if let Some(command) = commands.iter_mut().find(|command| {
|
|
||||||
let cmd_str = command.as_string().unwrap_or_default();
|
|
||||||
cmd_str.starts_with(&raw_input[..alias_start])
|
|
||||||
}) {
|
|
||||||
*command = alias;
|
|
||||||
} else {
|
|
||||||
commands.push(alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let table = UntaggedValue::table(&[alias]);
|
|
||||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config::write(&result, &None)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for item in list.iter() {
|
|
||||||
if let Ok(string) = item.as_string() {
|
|
||||||
processed_args.push(format!("${}", string));
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"Expected a string",
|
|
||||||
"expected a string",
|
|
||||||
item.tag(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
|
||||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Alias;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
test_examples(Alias {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
240
crates/nu-cli/src/commands/ansi.rs
Normal file
240
crates/nu-cli/src/commands/ansi.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use ansi_term::Color;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Ansi;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AnsiArgs {
|
||||||
|
color: Value,
|
||||||
|
escape: Option<Tagged<String>>,
|
||||||
|
osc: Option<Tagged<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Ansi {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ansi"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ansi")
|
||||||
|
.optional(
|
||||||
|
"color",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the name of the color to use or 'reset' to reset the color",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"escape", // \x1b
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"escape sequence without the escape character(s)",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"osc",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||||
|
Some('o'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
r#"Output ANSI codes to change color
|
||||||
|
|
||||||
|
For escape sequences:
|
||||||
|
Escape: '\x1b[' is not required for --escape parameter
|
||||||
|
Format: #(;#)m
|
||||||
|
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
|
||||||
|
There can be multiple text formatting sequence numbers
|
||||||
|
separated by a ; and ending with an m where the # is of the
|
||||||
|
following values:
|
||||||
|
attributes
|
||||||
|
0 reset / normal display
|
||||||
|
1 bold or increased intensity
|
||||||
|
2 faint or decreased intensity
|
||||||
|
3 italic on (non-mono font)
|
||||||
|
4 underline on
|
||||||
|
5 slow blink on
|
||||||
|
6 fast blink on
|
||||||
|
7 reverse video on
|
||||||
|
8 nondisplayed (invisible) on
|
||||||
|
9 strike-through on
|
||||||
|
|
||||||
|
foreground/bright colors background/bright colors
|
||||||
|
30/90 black 40/100 black
|
||||||
|
31/91 red 41/101 red
|
||||||
|
32/92 green 42/102 green
|
||||||
|
33/93 yellow 43/103 yellow
|
||||||
|
34/94 blue 44/104 blue
|
||||||
|
35/95 magenta 45/105 magenta
|
||||||
|
36/96 cyan 46/106 cyan
|
||||||
|
37/97 white 47/107 white
|
||||||
|
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
|
OSC: '\x1b]' is not required for --osc parameter
|
||||||
|
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
|
||||||
|
Format: #
|
||||||
|
0 Set window title and icon name
|
||||||
|
1 Set icon name
|
||||||
|
2 Set window title
|
||||||
|
4 Set/read color palette
|
||||||
|
9 iTerm2 Grown notifications
|
||||||
|
10 Set foreground color (x11 color spec)
|
||||||
|
11 Set background color (x11 color spec)
|
||||||
|
... others"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
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",
|
||||||
|
)]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||||
|
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||||
|
result: Some(vec![Value::from(
|
||||||
|
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||||
|
)]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let (AnsiArgs { color, escape, osc }, _) = args.process().await?;
|
||||||
|
|
||||||
|
if let Some(e) = escape {
|
||||||
|
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||||
|
if esc_vec[0] == '\\' {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"no need for escape characters",
|
||||||
|
"no need for escape characters",
|
||||||
|
e.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let output = format!("\x1b[{}", e.item);
|
||||||
|
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(e.tag()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(o) = osc {
|
||||||
|
let osc_vec: Vec<char> = o.item.chars().collect();
|
||||||
|
if osc_vec[0] == '\\' {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"no need for escape characters",
|
||||||
|
"no need for escape characters",
|
||||||
|
o.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
|
||||||
|
// OCS's need to end with a bell '\x07' char
|
||||||
|
let output = format!("\x1b]{};", o.item);
|
||||||
|
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(o.tag()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let color_string = color.as_string()?;
|
||||||
|
let ansi_code = str_to_ansi_color(color_string);
|
||||||
|
|
||||||
|
if let Some(output) = ansi_code {
|
||||||
|
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,17 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AppendArgs {
|
struct Arguments {
|
||||||
row: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Append;
|
pub struct Command;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Append {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"append"
|
"append"
|
||||||
}
|
}
|
||||||
@@ -26,24 +25,44 @@ impl WholeStreamCommand for Append {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Append the given row to the table"
|
"Append a row to the table"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
let (Arguments { mut value }, input) = args.process().await?;
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let (AppendArgs { 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) {
|
||||||
|
value.tag = first.tag();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if we are trying to append a row literal
|
||||||
|
if let Value {
|
||||||
|
value: UntaggedValue::Table(values),
|
||||||
|
tag,
|
||||||
|
} = &value
|
||||||
|
{
|
||||||
|
if values.len() == 1 && values[0].is_row() {
|
||||||
|
value = values[0].value.clone().into_value(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(
|
||||||
|
input
|
||||||
|
.into_iter()
|
||||||
|
.chain(vec![value])
|
||||||
|
.map(ReturnSuccess::value),
|
||||||
|
)
|
||||||
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
use nu_protocol::row;
|
||||||
description: "Add something to the end of a list or table",
|
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Add values to the end of the table",
|
||||||
example: "echo [1 2 3] | append 4",
|
example: "echo [1 2 3] | append 4",
|
||||||
result: Some(vec![
|
result: Some(vec![
|
||||||
UntaggedValue::int(1).into(),
|
UntaggedValue::int(1).into(),
|
||||||
@@ -51,18 +70,16 @@ impl WholeStreamCommand for Append {
|
|||||||
UntaggedValue::int(3).into(),
|
UntaggedValue::int(3).into(),
|
||||||
UntaggedValue::int(4).into(),
|
UntaggedValue::int(4).into(),
|
||||||
]),
|
]),
|
||||||
}]
|
},
|
||||||
}
|
Example {
|
||||||
}
|
description: "Add row value to the end of the table",
|
||||||
|
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||||
#[cfg(test)]
|
result: Some(vec![
|
||||||
mod tests {
|
row! { "country".into() => Value::from("Ecuador")},
|
||||||
use super::Append;
|
row! { "country".into() => Value::from("New Zealand")},
|
||||||
|
row! { "country".into() => Value::from("USA")},
|
||||||
#[test]
|
]),
|
||||||
fn examples_work_as_expected() {
|
},
|
||||||
use crate::examples::test as test_examples;
|
]
|
||||||
|
|
||||||
test_examples(Append {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
87
crates/nu-cli/src/commands/autoenv.rs
Normal file
87
crates/nu-cli/src/commands/autoenv.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, &args.scope))
|
||||||
|
.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,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
80
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
80
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let ctx = EvaluationContext::from_args(&args);
|
||||||
|
|
||||||
|
let file_to_trust = match args.call_info.evaluate(&ctx).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,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
104
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
104
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let ctx = EvaluationContext::from_args(&args);
|
||||||
|
let file_to_untrust = match args.call_info.evaluate(&ctx).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,17 +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, Signature, UntaggedValue, Value};
|
||||||
|
use nu_table::TextStyle;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
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"
|
||||||
}
|
}
|
||||||
@@ -24,20 +26,15 @@ impl WholeStreamCommand for Autoview {
|
|||||||
"View the contents of the pipeline as a table or list."
|
"View the contents of the pipeline as a table or list."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
autoview(RunnableContext {
|
autoview(RunnableContext {
|
||||||
input: args.input,
|
input: args.input,
|
||||||
registry: registry.clone(),
|
scope: args.scope.clone(),
|
||||||
shell_manager: args.shell_manager,
|
shell_manager: args.shell_manager,
|
||||||
host: args.host,
|
host: args.host,
|
||||||
ctrl_c: args.ctrl_c,
|
ctrl_c: args.ctrl_c,
|
||||||
current_errors: args.current_errors,
|
current_errors: args.current_errors,
|
||||||
name: args.call_info.name_tag,
|
name: args.call_info.name_tag,
|
||||||
raw_input: args.raw_input,
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -63,7 +60,7 @@ pub struct RunnableContextWithoutInput {
|
|||||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||||
pub ctrl_c: Arc<AtomicBool>,
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
pub registry: CommandRegistry,
|
pub scope: Scope,
|
||||||
pub name: Tag,
|
pub name: Tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +71,7 @@ impl RunnableContextWithoutInput {
|
|||||||
host: context.host,
|
host: context.host,
|
||||||
ctrl_c: context.ctrl_c,
|
ctrl_c: context.ctrl_c,
|
||||||
current_errors: context.current_errors,
|
current_errors: context.current_errors,
|
||||||
registry: context.registry,
|
scope: context.scope,
|
||||||
name: context.name,
|
name: context.name,
|
||||||
};
|
};
|
||||||
(context.input, new_context)
|
(context.input, new_context)
|
||||||
@@ -82,30 +79,17 @@ 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 {
|
||||||
@@ -120,7 +104,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
|
|
||||||
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).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +121,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).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{}", s);
|
out!("{}", s);
|
||||||
@@ -160,7 +144,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).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{}\n", s);
|
out!("{}\n", s);
|
||||||
@@ -235,7 +219,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).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
use pretty_hex::*;
|
use pretty_hex::*;
|
||||||
@@ -253,8 +237,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()
|
||||||
@@ -263,22 +247,21 @@ 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 mut entries = vec![];
|
let mut entries = vec![];
|
||||||
for (key, value) in row.entries.iter() {
|
for (key, value) in row.entries.iter() {
|
||||||
entries.push(vec![
|
entries.push(vec![
|
||||||
nu_table::StyledString::new(
|
nu_table::StyledString::new(
|
||||||
key.to_string(),
|
key.to_string(),
|
||||||
nu_table::TextStyle {
|
TextStyle::new()
|
||||||
alignment: nu_table::Alignment::Left,
|
.alignment(nu_table::Alignment::Left)
|
||||||
color: Some(ansi_term::Color::Green),
|
.fg(ansi_term::Color::Green)
|
||||||
is_bold: true,
|
.bold(Some(true)),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
nu_table::StyledString::new(
|
nu_table::StyledString::new(
|
||||||
format_leaf(value).plain_string(100_000),
|
format_leaf(value).plain_string(100_000),
|
||||||
nu_table::TextStyle::basic(),
|
nu_table::TextStyle::basic_left(),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -286,9 +269,14 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
|||||||
let table =
|
let table =
|
||||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||||
|
|
||||||
nu_table::draw_table(&table, textwrap::termwidth());
|
nu_table::draw_table(&table, term_width, &color_hm);
|
||||||
|
}
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
Value {
|
Value {
|
||||||
value: ref item, ..
|
value: ref item, ..
|
||||||
} => {
|
} => {
|
||||||
@@ -297,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).await?;
|
||||||
result.collect::<Vec<_>>().await;
|
result.collect::<Vec<_>>().await;
|
||||||
} else {
|
} else {
|
||||||
out!("{:?}", item);
|
out!("{:?}", item);
|
||||||
@@ -327,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::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
210
crates/nu-cli/src/commands/benchmark.rs
Normal file
210
crates/nu-cli/src/commands/benchmark.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
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, CapturedBlock, ClassifiedCommand, Group, InternalCommand, Pipeline},
|
||||||
|
Dictionary, 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: CapturedBlock,
|
||||||
|
passthrough: Option<CapturedBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) -> Result<OutputStream, ShellError> {
|
||||||
|
benchmark(args).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) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = raw_args.call_info.args.span;
|
||||||
|
let mut context = EvaluationContext::from_raw(&raw_args);
|
||||||
|
let scope = raw_args.scope.clone();
|
||||||
|
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process().await?;
|
||||||
|
|
||||||
|
let env = scope.get_env_vars();
|
||||||
|
let name = generate_free_name(&env);
|
||||||
|
|
||||||
|
scope.add_env_var(name, generate_random_env_value());
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
let start = time().await;
|
||||||
|
|
||||||
|
context.scope.enter_scope();
|
||||||
|
let result = run_block(&block.block, &context, input).await;
|
||||||
|
context.scope.exit_scope();
|
||||||
|
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).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).await
|
||||||
|
} else {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Could not retrieve CPU time",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn benchmark_output<T, Output>(
|
||||||
|
indexmap: IndexMap<String, BigInt>,
|
||||||
|
block_output: Output,
|
||||||
|
passthrough: Option<CapturedBlock>,
|
||||||
|
tag: T,
|
||||||
|
context: &mut EvaluationContext,
|
||||||
|
) -> 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.block);
|
||||||
|
|
||||||
|
context.scope.enter_scope();
|
||||||
|
let result = run_block(&time_block, context, benchmark_output).await;
|
||||||
|
context.scope.exit_scope();
|
||||||
|
result?;
|
||||||
|
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() {
|
||||||
|
let group = Group::new(
|
||||||
|
vec![{
|
||||||
|
let mut commands = Pipeline::new(block.span);
|
||||||
|
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
|
||||||
|
"autoview".to_string(),
|
||||||
|
block.span,
|
||||||
|
block.span,
|
||||||
|
)));
|
||||||
|
commands
|
||||||
|
}],
|
||||||
|
block.span,
|
||||||
|
);
|
||||||
|
block.push(group);
|
||||||
|
}
|
||||||
|
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)]
|
||||||
@@ -27,13 +27,9 @@ impl WholeStreamCommand for BuildString {
|
|||||||
"Builds a string from the arguments"
|
"Builds a string from the arguments"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (BuildStringArgs { rest }, _) = args.process(®istry).await?;
|
let (BuildStringArgs { rest }, _) = args.process().await?;
|
||||||
|
|
||||||
let mut output_string = String::new();
|
let mut output_string = String::new();
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,8 @@ impl WholeStreamCommand for Cal {
|
|||||||
"Display a calendar."
|
"Display a calendar."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
cal(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
cal(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -70,12 +66,8 @@ impl WholeStreamCommand for Cal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cal(
|
pub async fn cal(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let args = args.evaluate_once().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let args = args.evaluate_once(®istry).await?;
|
|
||||||
let mut calendar_vec_deque = VecDeque::new();
|
let mut calendar_vec_deque = VecDeque::new();
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
@@ -84,8 +76,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;
|
||||||
|
|
||||||
@@ -95,14 +86,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,
|
||||||
@@ -110,12 +100,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 {
|
||||||
@@ -300,7 +287,7 @@ fn add_month_to_table(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.month_name.clone()).into_value(tag)
|
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
|
||||||
} else {
|
} else {
|
||||||
@@ -344,11 +331,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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,14 +32,10 @@ impl WholeStreamCommand for Cd {
|
|||||||
"Change to a new path."
|
"Change to a new path."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let shell_manager = args.shell_manager.clone();
|
let shell_manager = args.shell_manager.clone();
|
||||||
let (args, _): (CdArgs, _) = args.process(®istry).await?;
|
let (args, _): (CdArgs, _) = args.process().await?;
|
||||||
shell_manager.cd(args, name)
|
shell_manager.cd(args, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +68,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
166
crates/nu-cli/src/commands/char_.rs
Normal file
166
crates/nu-cli/src/commands/char_.rs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
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>,
|
||||||
|
unicode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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",
|
||||||
|
)
|
||||||
|
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Output unicode character",
|
||||||
|
example: r#"char -u 1f378"#,
|
||||||
|
result: Some(vec![Value::from("\u{1f378}")]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let (CharArgs { name, unicode }, _) = args.process().await?;
|
||||||
|
|
||||||
|
if unicode {
|
||||||
|
let decoded_char = string_to_unicode_char(&name.item);
|
||||||
|
if let Some(output) = decoded_char {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"error decoding unicode character",
|
||||||
|
"error decoding unicode character",
|
||||||
|
name.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let special_character = str_to_character(&name.item);
|
||||||
|
if let Some(output) = special_character {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"error finding named character",
|
||||||
|
"error finding named character",
|
||||||
|
name.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||||
|
u32::from_str_radix(s, 16)
|
||||||
|
.ok()
|
||||||
|
.and_then(std::char::from_u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_to_character(s: &str) -> Option<String> {
|
||||||
|
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)
|
||||||
|
// Unicode can't be mixed with Ansi or it will break width calculation
|
||||||
|
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||||
|
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||||
|
|
||||||
|
"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" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||||
|
"moon" => Some("🌛".to_string()),
|
||||||
|
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||||
|
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||||
|
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||||
|
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||||
|
"snowy" | "snow" => Some("❄️".to_string()),
|
||||||
|
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||||
|
|
||||||
|
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||||
|
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||||
|
|
||||||
|
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||||
|
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
|
||||||
|
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||||
|
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||||
|
|
||||||
|
// Ansi Erase Sequences
|
||||||
|
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||||
|
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
|
||||||
|
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
|
||||||
|
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
|
||||||
|
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
|
||||||
|
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||||
|
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||||
|
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
48
crates/nu-cli/src/commands/chart.rs
Normal file
48
crates/nu-cli/src/commands/chart.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
if args.scope.get_command("chart bar").is_none() {
|
||||||
|
return Err(ShellError::untagged_runtime_error(
|
||||||
|
"nu_plugin_chart not installed.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Chart, &args.scope))
|
||||||
|
.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,42 +1,69 @@
|
|||||||
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 async_recursion::async_recursion;
|
||||||
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, Call, ClassifiedCommand, Expression, Pipeline, SpannedExpression, Synthetic,
|
||||||
|
};
|
||||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||||
|
use nu_stream::InputStream;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(crate) async fn run_block(
|
#[async_recursion]
|
||||||
|
pub async fn run_block(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
ctx: &mut Context,
|
ctx: &EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
|
||||||
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 (_, definition) in block.definitions.iter() {
|
||||||
|
ctx.scope.add_definition(definition.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in &block.block {
|
||||||
match output {
|
match output {
|
||||||
Ok(inp) if inp.is_empty() => {}
|
Ok(inp) if inp.is_empty() => {}
|
||||||
Ok(inp) => {
|
Ok(inp) => {
|
||||||
let mut output_stream = inp.to_output_stream();
|
// Run autoview on the values we've seen so far
|
||||||
|
// We may want to make this configurable for other kinds of hosting
|
||||||
loop {
|
if let Some(autoview) = ctx.get_command("autoview") {
|
||||||
|
let mut output_stream = match ctx
|
||||||
|
.run_command(
|
||||||
|
autoview,
|
||||||
|
Tag::unknown(),
|
||||||
|
Call::new(
|
||||||
|
Box::new(SpannedExpression::new(
|
||||||
|
Expression::Synthetic(Synthetic::String("autoview".into())),
|
||||||
|
Span::unknown(),
|
||||||
|
)),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
inp,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
match output_stream.try_next().await {
|
match output_stream.try_next().await {
|
||||||
Ok(Some(ReturnSuccess::Value(Value {
|
Ok(Some(ReturnSuccess::Value(Value {
|
||||||
value: UntaggedValue::Error(e),
|
value: UntaggedValue::Error(e),
|
||||||
..
|
..
|
||||||
}))) => return Err(e),
|
}))) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
Ok(Some(_item)) => {
|
Ok(Some(_item)) => {
|
||||||
if let Some(err) = ctx.get_errors().get(0) {
|
if let Some(err) = ctx.get_errors().get(0) {
|
||||||
ctx.clear_errors();
|
ctx.clear_errors();
|
||||||
return Err(err.clone());
|
return Err(err.clone());
|
||||||
}
|
}
|
||||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||||
break;
|
return Ok(InputStream::empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
@@ -44,9 +71,10 @@ pub(crate) async fn run_block(
|
|||||||
ctx.clear_errors();
|
ctx.clear_errors();
|
||||||
return Err(err.clone());
|
return Err(err.clone());
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e),
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,43 +82,131 @@ pub(crate) async fn run_block(
|
|||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
output = Ok(InputStream::empty());
|
||||||
|
for pipeline in &group.pipelines {
|
||||||
|
match output {
|
||||||
|
Ok(inp) if inp.is_empty() => {}
|
||||||
|
Ok(inp) => {
|
||||||
|
let mut output_stream = inp.to_output_stream();
|
||||||
|
|
||||||
|
match output_stream.try_next().await {
|
||||||
|
Ok(Some(ReturnSuccess::Value(Value {
|
||||||
|
value: UntaggedValue::Error(e),
|
||||||
|
..
|
||||||
|
}))) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(Some(_item)) => {
|
||||||
|
if let Some(err) = ctx.get_errors().get(0) {
|
||||||
|
ctx.clear_errors();
|
||||||
|
return Err(err.clone());
|
||||||
|
}
|
||||||
|
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||||
|
// This early return doesn't return the result
|
||||||
|
// we have so far, but breaking out of this loop
|
||||||
|
// causes lifetime issues. A future contribution
|
||||||
|
// could attempt to return the current output.
|
||||||
|
// https://github.com/nushell/nushell/pull/2830#discussion_r550319687
|
||||||
|
return Ok(InputStream::empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
if let Some(err) = ctx.get_errors().get(0) {
|
||||||
|
ctx.clear_errors();
|
||||||
|
return Err(err.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output = run_pipeline(pipeline, ctx, input).await;
|
||||||
|
|
||||||
input = InputStream::empty();
|
input = InputStream::empty();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_recursion]
|
||||||
async fn run_pipeline(
|
async fn run_pipeline(
|
||||||
commands: &Commands,
|
commands: &Pipeline,
|
||||||
ctx: &mut Context,
|
ctx: &EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
|
||||||
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(call) => {
|
||||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
let mut args = vec![];
|
||||||
|
if let Some(positional) = call.positional {
|
||||||
input = match (item, next) {
|
for pos in &positional {
|
||||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
let result = run_expression_block(pos, ctx).await?.into_vec().await;
|
||||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
args.push(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
match &call.head.expr {
|
||||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
Expression::Block(block) => {
|
||||||
|
ctx.scope.enter_scope();
|
||||||
|
for (param, value) in block.params.positional.iter().zip(args.iter()) {
|
||||||
|
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||||
}
|
}
|
||||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
let result = run_block(&block, ctx, input).await;
|
||||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
ctx.scope.exit_scope();
|
||||||
|
|
||||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
let result = result?;
|
||||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
return Ok(result);
|
||||||
|
}
|
||||||
|
Expression::Variable(v, span) => {
|
||||||
|
if let Some(value) = ctx.scope.get_var(v) {
|
||||||
|
match &value.value {
|
||||||
|
UntaggedValue::Block(captured_block) => {
|
||||||
|
ctx.scope.enter_scope();
|
||||||
|
ctx.scope.add_vars(&captured_block.captured.entries);
|
||||||
|
for (param, value) in captured_block
|
||||||
|
.block
|
||||||
|
.params
|
||||||
|
.positional
|
||||||
|
.iter()
|
||||||
|
.zip(args.iter())
|
||||||
|
{
|
||||||
|
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||||
|
}
|
||||||
|
let result = run_block(&captured_block.block, ctx, input).await;
|
||||||
|
ctx.scope.exit_scope();
|
||||||
|
|
||||||
|
let result = result?;
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Variable not found",
|
||||||
|
"variable not found",
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, _) => break,
|
ClassifiedCommand::Expr(expr) => run_expression_block(&*expr, ctx).await?,
|
||||||
|
|
||||||
|
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||||
|
|
||||||
|
ClassifiedCommand::Internal(left) => run_internal_command(left, ctx, input).await?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,17 @@ 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;
|
|
||||||
|
|
||||||
pub(crate) async fn run_expression_block(
|
pub(crate) async fn run_expression_block(
|
||||||
expr: SpannedExpression,
|
expr: &SpannedExpression,
|
||||||
context: &mut Context,
|
ctx: &EvaluationContext,
|
||||||
it: &Value,
|
|
||||||
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", "->");
|
||||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
let registry = context.registry().clone();
|
let output = evaluate_baseline_expr(expr, ctx).await?;
|
||||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
|
||||||
|
|
||||||
Ok(once(async { Ok(output) }).to_input_stream())
|
Ok(once(async { Ok(output) }).to_input_stream())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +1,33 @@
|
|||||||
|
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::*;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
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::Expression;
|
||||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||||
|
use nu_protocol::{Primitive, 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,
|
external_redirection: ExternalRedirection,
|
||||||
is_last: bool,
|
|
||||||
) -> 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 +35,14 @@ pub(crate) async fn run_external_command(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
run_with_stdin(command, context, input, scope, is_last).await
|
run_with_stdin(command, context, input, 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,
|
external_redirection: ExternalRedirection,
|
||||||
is_last: bool,
|
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let path = context.shell_manager.path();
|
let path = context.shell_manager.path();
|
||||||
|
|
||||||
@@ -115,43 +50,65 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
let mut command_args = vec![];
|
let mut command_args = vec![];
|
||||||
for arg in command.args.iter() {
|
for arg in command.args.iter() {
|
||||||
let value =
|
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
let value = evaluate_baseline_expr(arg, context).await?;
|
||||||
.await?;
|
|
||||||
// Skip any arguments that don't really exist, treating them as optional
|
// Skip any arguments that don't really exist, treating them as optional
|
||||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||||
// 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(),
|
||||||
|
is_literal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
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, is_literal));
|
||||||
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, _is_literal)| {
|
||||||
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))]
|
||||||
{
|
{
|
||||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
if !_is_literal {
|
||||||
add_quotes(&arg)
|
let escaped = escape_double_quotes(&arg);
|
||||||
|
add_double_quotes(&escaped)
|
||||||
} else {
|
} else {
|
||||||
arg.as_ref().to_string()
|
arg.as_ref().to_string()
|
||||||
}
|
}
|
||||||
@@ -167,7 +124,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,
|
||||||
|
&context.scope,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
@@ -175,7 +139,7 @@ fn spawn(
|
|||||||
path: &str,
|
path: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
is_last: bool,
|
external_redirection: ExternalRedirection,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
@@ -207,14 +171,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.get_env_vars());
|
||||||
|
|
||||||
// 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() {
|
||||||
@@ -246,36 +223,19 @@ fn spawn(
|
|||||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||||
UntaggedValue::Primitive(Primitive::String(s))
|
UntaggedValue::Primitive(Primitive::String(s))
|
||||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
if stdin_write.write(s.as_bytes()).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
message,
|
|
||||||
"application may have closed before completing pipeline",
|
|
||||||
&stdin_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdin_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||||
if let Err(e) = stdin_write.write(b) {
|
if stdin_write.write(b).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
message,
|
|
||||||
"application may have closed before completing pipeline",
|
|
||||||
&stdin_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdin_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsupported => {
|
unsupported => {
|
||||||
|
println!("Unsupported: {:?}", unsupported);
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
let _ = stdin_write_tx.send(Ok(Value {
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
format!(
|
format!(
|
||||||
@@ -297,7 +257,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 +275,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 +328,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 +410,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 +443,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)
|
||||||
@@ -439,11 +481,6 @@ where
|
|||||||
shellexpand::tilde_with_context(input, home_dir)
|
shellexpand::tilde_with_context(input, home_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
|
||||||
argument.chars().any(|c| c.is_whitespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn argument_is_quoted(argument: &str) -> bool {
|
fn argument_is_quoted(argument: &str) -> bool {
|
||||||
if argument.len() < 2 {
|
if argument.len() < 2 {
|
||||||
return false;
|
return false;
|
||||||
@@ -454,10 +491,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn add_quotes(argument: &str) -> String {
|
fn add_double_quotes(argument: &str) -> String {
|
||||||
format!("\"{}\"", argument)
|
format!("\"{}\"", argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||||
|
// allocate new string only if required
|
||||||
|
if argument.contains('"') {
|
||||||
|
Cow::Owned(argument.replace('"', r#"\""#))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||||
if !argument_is_quoted(argument) {
|
if !argument_is_quoted(argument) {
|
||||||
@@ -483,12 +530,16 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
||||||
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;
|
||||||
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,14 +555,17 @@ 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(cmd, &mut ctx, input, &Scope::new(), false)
|
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
@@ -522,7 +576,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,16 +597,17 @@ 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
fn checks_escape_double_quotes() {
|
||||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -580,9 +635,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,32 +1,31 @@
|
|||||||
use crate::commands::command::whole_stream_command;
|
use std::sync::atomic::Ordering;
|
||||||
use crate::commands::run_alias::AliasCommand;
|
|
||||||
use crate::commands::UnevaluatedCallInfo;
|
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, UntaggedValue, Value};
|
||||||
|
|
||||||
pub(crate) async fn run_internal_command(
|
pub(crate) async fn run_internal_command(
|
||||||
command: InternalCommand,
|
command: InternalCommand,
|
||||||
context: &mut Context,
|
context: &EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
it: &Value,
|
|
||||||
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.scope.expect_command(&command.name);
|
||||||
|
|
||||||
|
if command.name == "autoenv untrust" {
|
||||||
|
context
|
||||||
|
.user_recently_used_autoenv_untrust
|
||||||
|
.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
context
|
context
|
||||||
@@ -34,44 +33,38 @@ pub(crate) async fn run_internal_command(
|
|||||||
internal_command?,
|
internal_command?,
|
||||||
Tag::unknown_anchor(command.name_span),
|
Tag::unknown_anchor(command.name_span),
|
||||||
command.args.clone(),
|
command.args.clone(),
|
||||||
&scope,
|
|
||||||
objects,
|
objects,
|
||||||
)
|
)
|
||||||
.await
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
let head = Arc::new(command.args.head.clone());
|
let head = Arc::new(command.args.head.clone());
|
||||||
//let context = Arc::new(context.clone());
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let command = Arc::new(command);
|
let command = Arc::new(command);
|
||||||
let scope = Arc::new(scope);
|
|
||||||
// let scope = scope.clone();
|
|
||||||
|
|
||||||
Ok(InputStream::from_stream(
|
Ok(InputStream::from_stream(
|
||||||
result
|
result
|
||||||
.then(move |item| {
|
.then(move |item| {
|
||||||
let head = head.clone();
|
let head = head.clone();
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
let mut context = context.clone();
|
let context = context.clone();
|
||||||
let scope = scope.clone();
|
|
||||||
async move {
|
async move {
|
||||||
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![]))
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
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.clone());
|
context.error(err);
|
||||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
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.scope.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(),
|
||||||
@@ -83,18 +76,18 @@ pub(crate) async fn run_internal_command(
|
|||||||
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: context.scope.clone(),
|
||||||
};
|
};
|
||||||
let mut result = converter
|
let result = converter
|
||||||
.run(
|
.run(new_args.with_input(vec![tagged_contents]))
|
||||||
new_args.with_input(vec![tagged_contents]),
|
|
||||||
&context.registry,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(mut result) => {
|
||||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
result.drain_vec().await;
|
result.drain_vec().await;
|
||||||
|
|
||||||
@@ -109,10 +102,13 @@ pub(crate) async fn run_internal_command(
|
|||||||
output.push(Ok(l));
|
output.push(Ok(l));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ReturnSuccess::Value(Value { value, .. })) => {
|
Ok(ReturnSuccess::Value(Value {
|
||||||
output.push(Ok(
|
value,
|
||||||
value.into_value(contents_tag.clone())
|
..
|
||||||
));
|
})) => {
|
||||||
|
output
|
||||||
|
.push(Ok(value
|
||||||
|
.into_value(contents_tag.clone())));
|
||||||
}
|
}
|
||||||
Err(e) => output.push(Err(e)),
|
Err(e) => output.push(Err(e)),
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -120,6 +116,12 @@ pub(crate) async fn run_internal_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
futures::stream::iter(output).to_input_stream()
|
futures::stream::iter(output).to_input_stream()
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
context.error(err);
|
||||||
|
InputStream::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
InputStream::one(tagged_contents)
|
InputStream::one(tagged_contents)
|
||||||
}
|
}
|
||||||
@@ -132,13 +134,12 @@ pub(crate) async fn run_internal_command(
|
|||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
match HelpShell::for_command(
|
match HelpShell::for_command(
|
||||||
UntaggedValue::string(cmd).into_value(tag),
|
UntaggedValue::string(cmd).into_value(tag),
|
||||||
&context.registry(),
|
&context.scope,
|
||||||
) {
|
) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return InputStream::one(
|
context.error(err);
|
||||||
UntaggedValue::Error(err).into_untagged_value(),
|
return InputStream::empty();
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
@@ -146,12 +147,11 @@ pub(crate) async fn run_internal_command(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
match HelpShell::index(&context.registry()) {
|
match HelpShell::index(&context.scope) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return InputStream::one(
|
context.error(err);
|
||||||
UntaggedValue::Error(err).into_untagged_value(),
|
return InputStream::empty();
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
@@ -166,50 +166,59 @@ pub(crate) async fn run_internal_command(
|
|||||||
}
|
}
|
||||||
CommandAction::EnterShell(location) => {
|
CommandAction::EnterShell(location) => {
|
||||||
context.shell_manager.insert_at_current(Box::new(
|
context.shell_manager.insert_at_current(Box::new(
|
||||||
match FilesystemShell::with_location(
|
match FilesystemShell::with_location(location) {
|
||||||
location,
|
|
||||||
context.registry().clone(),
|
|
||||||
) {
|
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return InputStream::one(
|
context.error(err.into());
|
||||||
UntaggedValue::Error(err.into())
|
return InputStream::empty();
|
||||||
.into_untagged_value(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::AddAlias(name, args, block) => {
|
CommandAction::AddPlugins(path) => {
|
||||||
context.add_commands(vec![whole_stream_command(
|
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||||
AliasCommand::new(name, args, block),
|
Ok(plugins) => {
|
||||||
)]);
|
context.add_commands(
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| {
|
||||||
|
!context.is_command_registered(p.name())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
InputStream::empty()
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
context.error(reason);
|
||||||
|
InputStream::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CommandAction::PreviousShell => {
|
CommandAction::PreviousShell => {
|
||||||
context.shell_manager.prev();
|
context.shell_manager.prev();
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
CommandAction::NextShell => {
|
CommandAction::NextShell => {
|
||||||
context.shell_manager.next();
|
context.shell_manager.next();
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
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![]))
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(Value {
|
Ok(ReturnSuccess::Value(Value {
|
||||||
value: UntaggedValue::Error(err),
|
value: UntaggedValue::Error(err),
|
||||||
tag,
|
..
|
||||||
})) => {
|
})) => {
|
||||||
context.error(err.clone());
|
context.error(err);
|
||||||
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||||
@@ -229,8 +238,8 @@ pub(crate) async fn run_internal_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
context.error(err.clone());
|
context.error(err);
|
||||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
InputStream::empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
|||||||
@@ -1,30 +1,16 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::command::{whole_stream_command, WholeStreamCommand};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::jsonrpc::JsonRpc;
|
||||||
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||||
use serde::{self, Deserialize, Serialize};
|
use serde::{self, Deserialize, Serialize};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
use std::process::{Child, Command, Stdio};
|
||||||
pub struct JsonRpc<T> {
|
|
||||||
jsonrpc: String,
|
|
||||||
pub method: String,
|
|
||||||
pub params: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> JsonRpc<T> {
|
|
||||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
|
||||||
JsonRpc {
|
|
||||||
jsonrpc: "2.0".into(),
|
|
||||||
method: method.into(),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "method")]
|
#[serde(tag = "method")]
|
||||||
@@ -35,15 +21,77 @@ pub enum NuResult {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PluginCommand {
|
||||||
|
Filter(PluginFilter),
|
||||||
|
Sink(PluginSink),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginCommand {
|
||||||
|
fn command(self) -> Result<crate::commands::Command, ShellError> {
|
||||||
|
match self {
|
||||||
|
PluginCommand::Filter(cmd) => Ok(whole_stream_command(cmd)),
|
||||||
|
PluginCommand::Sink(cmd) => Ok(whole_stream_command(cmd)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PluginMode {
|
||||||
|
Filter,
|
||||||
|
Sink,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PluginCommandBuilder {
|
||||||
|
mode: PluginMode,
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
config: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginCommandBuilder {
|
||||||
|
pub fn new(
|
||||||
|
name: impl Into<String>,
|
||||||
|
path: impl Into<String>,
|
||||||
|
config: impl Into<Signature>,
|
||||||
|
) -> Self {
|
||||||
|
let config = config.into();
|
||||||
|
|
||||||
|
PluginCommandBuilder {
|
||||||
|
mode: if config.is_filter {
|
||||||
|
PluginMode::Filter
|
||||||
|
} else {
|
||||||
|
PluginMode::Sink
|
||||||
|
},
|
||||||
|
name: name.into(),
|
||||||
|
path: path.into(),
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result<crate::commands::Command, ShellError> {
|
||||||
|
let mode = &self.mode;
|
||||||
|
|
||||||
|
let name = self.name.clone();
|
||||||
|
let path = self.path.clone();
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
let cmd = match mode {
|
||||||
|
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
|
||||||
|
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.command()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct PluginCommand {
|
pub struct PluginFilter {
|
||||||
name: String,
|
name: String,
|
||||||
path: String,
|
path: String,
|
||||||
config: Signature,
|
config: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PluginCommand {
|
impl WholeStreamCommand for PluginFilter {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
@@ -56,24 +104,13 @@ impl WholeStreamCommand for PluginCommand {
|
|||||||
&self.config.usage
|
&self.config.usage
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
run_filter(self.path.clone(), (args)).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
filter_plugin(self.path.clone(), args, registry).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn filter_plugin(
|
async fn run_filter(path: String, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
path: String,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
trace!("filter_plugin :: {}", path);
|
trace!("filter_plugin :: {}", path);
|
||||||
let registry = registry.clone();
|
|
||||||
|
|
||||||
let scope = args.call_info.scope.clone();
|
|
||||||
|
|
||||||
let bos = futures::stream::iter(vec![
|
let bos = futures::stream::iter(vec![
|
||||||
UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()
|
UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()
|
||||||
@@ -82,13 +119,36 @@ pub async fn filter_plugin(
|
|||||||
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
let args = args.evaluate_once().await?;
|
||||||
|
|
||||||
let mut child = std::process::Command::new(path)
|
let real_path = Path::new(&path);
|
||||||
.stdin(std::process::Stdio::piped())
|
let ext = real_path.extension();
|
||||||
.stdout(std::process::Stdio::piped())
|
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()
|
.spawn()
|
||||||
.expect("Failed to spawn child process");
|
.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();
|
let call_info = args.call_info.clone();
|
||||||
|
|
||||||
@@ -111,6 +171,7 @@ pub async fn filter_plugin(
|
|||||||
|
|
||||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||||
let request_raw = serde_json::to_string(&request);
|
let request_raw = serde_json::to_string(&request);
|
||||||
|
trace!("begin_filter:request {:?}", &request_raw);
|
||||||
|
|
||||||
match request_raw {
|
match request_raw {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -136,6 +197,8 @@ pub async fn filter_plugin(
|
|||||||
match reader.read_line(&mut input) {
|
match reader.read_line(&mut input) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let response = serde_json::from_str::<NuResult>(&input);
|
let response = serde_json::from_str::<NuResult>(&input);
|
||||||
|
trace!("begin_filter:response {:?}", &response);
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(NuResult::response { params }) => match params {
|
Ok(NuResult::response { params }) => match params {
|
||||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||||
@@ -168,6 +231,7 @@ pub async fn filter_plugin(
|
|||||||
|
|
||||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||||
let request_raw = serde_json::to_string(&request);
|
let request_raw = serde_json::to_string(&request);
|
||||||
|
trace!("end_filter:request {:?}", &request_raw);
|
||||||
|
|
||||||
match request_raw {
|
match request_raw {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -193,6 +257,8 @@ pub async fn filter_plugin(
|
|||||||
let stream = match reader.read_line(&mut input) {
|
let stream = match reader.read_line(&mut input) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let response = serde_json::from_str::<NuResult>(&input);
|
let response = serde_json::from_str::<NuResult>(&input);
|
||||||
|
trace!("end_filter:response {:?}", &response);
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(NuResult::response { params }) => match params {
|
Ok(NuResult::response { params }) => match params {
|
||||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||||
@@ -220,6 +286,7 @@ pub async fn filter_plugin(
|
|||||||
|
|
||||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
||||||
let request_raw = serde_json::to_string(&request);
|
let request_raw = serde_json::to_string(&request);
|
||||||
|
trace!("quit:request {:?}", &request_raw);
|
||||||
|
|
||||||
match request_raw {
|
match request_raw {
|
||||||
Ok(request_raw) => {
|
Ok(request_raw) => {
|
||||||
@@ -246,6 +313,8 @@ pub async fn filter_plugin(
|
|||||||
|
|
||||||
let request = JsonRpc::new("filter", v);
|
let request = JsonRpc::new("filter", v);
|
||||||
let request_raw = serde_json::to_string(&request);
|
let request_raw = serde_json::to_string(&request);
|
||||||
|
trace!("filter:request {:?}", &request_raw);
|
||||||
|
|
||||||
match request_raw {
|
match request_raw {
|
||||||
Ok(request_raw) => {
|
Ok(request_raw) => {
|
||||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
|
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
|
||||||
@@ -262,6 +331,8 @@ pub async fn filter_plugin(
|
|||||||
match reader.read_line(&mut input) {
|
match reader.read_line(&mut input) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let response = serde_json::from_str::<NuResult>(&input);
|
let response = serde_json::from_str::<NuResult>(&input);
|
||||||
|
trace!("filter:response {:?}", &response);
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(NuResult::response { params }) => match params {
|
Ok(NuResult::response { params }) => match params {
|
||||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||||
@@ -308,22 +379,13 @@ impl WholeStreamCommand for PluginSink {
|
|||||||
&self.config.usage
|
&self.config.usage
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
run_sink(self.path.clone(), args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
sink_plugin(self.path.clone(), args, registry).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sink_plugin(
|
async fn run_sink(path: String, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
path: String,
|
let args = args.evaluate_once().await?;
|
||||||
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 call_info = args.call_info.clone();
|
||||||
|
|
||||||
let input: Vec<Value> = args.input.collect().await;
|
let input: Vec<Value> = args.input.collect().await;
|
||||||
@@ -335,7 +397,33 @@ pub async fn sink_plugin(
|
|||||||
let _ = writeln!(tmpfile, "{}", request_raw);
|
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||||
let _ = tmpfile.flush();
|
let _ = tmpfile.flush();
|
||||||
|
|
||||||
let child = std::process::Command::new(path).arg(tmpfile.path()).spawn();
|
let real_path = Path::new(&path);
|
||||||
|
let ext = real_path.extension();
|
||||||
|
let ps1_file = match ext {
|
||||||
|
Some(ext) => ext == "ps1",
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This sink may not work in powershell, trying to find
|
||||||
|
// an example of what CallInfo would look like in this temp file
|
||||||
|
let child = if ps1_file {
|
||||||
|
Command::new("pwsh")
|
||||||
|
.args(&[
|
||||||
|
"-NoLogo",
|
||||||
|
"-NoProfile",
|
||||||
|
"-ExecutionPolicy",
|
||||||
|
"Bypass",
|
||||||
|
"-File",
|
||||||
|
&real_path.to_string_lossy(),
|
||||||
|
&tmpfile
|
||||||
|
.path()
|
||||||
|
.to_str()
|
||||||
|
.expect("Failed getting tmpfile path"),
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
} else {
|
||||||
|
Command::new(path).arg(&tmpfile.path()).spawn()
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(mut child) = child {
|
if let Ok(mut child) = child {
|
||||||
let _ = child.wait();
|
let _ = child.wait();
|
||||||
@@ -17,10 +17,10 @@ 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) -> Result<OutputStream, ShellError> {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
Command::new("cmd")
|
Command::new("cmd")
|
||||||
.args(&["/C", "cls"])
|
.args(&["/C", "cls"])
|
||||||
@@ -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,11 +1,10 @@
|
|||||||
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;
|
||||||
use nu_protocol::{Signature, Value};
|
use nu_protocol::{Signature, Value};
|
||||||
|
|
||||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
use arboard::Clipboard;
|
||||||
|
|
||||||
pub struct Clip;
|
pub struct Clip;
|
||||||
|
|
||||||
@@ -23,60 +22,57 @@ impl WholeStreamCommand for Clip {
|
|||||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
clip(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
clip(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clip(
|
pub async fn clip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
|
||||||
_registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let input = args.input;
|
let input = args.input;
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let values: Vec<Value> = input.collect().await;
|
let values: Vec<Value> = input.collect().await;
|
||||||
|
|
||||||
if let Ok(clip_context) = ClipboardProvider::new() {
|
if let Ok(mut clip_context) = Clipboard::new() {
|
||||||
let mut clip_context: ClipboardContext = clip_context;
|
|
||||||
let mut new_copy_data = String::new();
|
let mut new_copy_data = String::new();
|
||||||
|
|
||||||
if !values.is_empty() {
|
if !values.is_empty() {
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in values.iter() {
|
for i in values.iter() {
|
||||||
if !first {
|
if !first {
|
||||||
new_copy_data.push_str("\n");
|
new_copy_data.push('\n');
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match clip_context.set_contents(new_copy_data) {
|
match clip_context.set_text(new_copy_data) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
@@ -99,11 +95,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,43 +1,26 @@
|
|||||||
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::*;
|
||||||
|
use crate::{commands::help::get_help, run_block};
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir;
|
use nu_protocol::hir::{self, Block};
|
||||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnevaluatedCallInfo {
|
impl UnevaluatedCallInfo {
|
||||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
pub async fn evaluate(self, ctx: &EvaluationContext) -> Result<CallInfo, ShellError> {
|
||||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
let args = evaluate_args(&self.args, ctx).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,
|
||||||
@@ -58,8 +41,8 @@ pub struct CommandArgs {
|
|||||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||||
pub shell_manager: ShellManager,
|
pub shell_manager: ShellManager,
|
||||||
pub call_info: UnevaluatedCallInfo,
|
pub call_info: UnevaluatedCallInfo,
|
||||||
|
pub scope: Scope,
|
||||||
pub input: InputStream,
|
pub input: InputStream,
|
||||||
pub raw_input: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Getters, Clone)]
|
#[derive(Getters, Clone)]
|
||||||
@@ -69,6 +52,7 @@ pub struct RawCommandArgs {
|
|||||||
pub ctrl_c: Arc<AtomicBool>,
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||||
pub shell_manager: ShellManager,
|
pub shell_manager: ShellManager,
|
||||||
|
pub scope: Scope,
|
||||||
pub call_info: UnevaluatedCallInfo,
|
pub call_info: UnevaluatedCallInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +64,8 @@ impl RawCommandArgs {
|
|||||||
current_errors: self.current_errors,
|
current_errors: self.current_errors,
|
||||||
shell_manager: self.shell_manager,
|
shell_manager: self.shell_manager,
|
||||||
call_info: self.call_info,
|
call_info: self.call_info,
|
||||||
|
scope: self.scope,
|
||||||
input: input.into(),
|
input: input.into(),
|
||||||
raw_input: String::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,15 +77,14 @@ impl std::fmt::Debug for CommandArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CommandArgs {
|
impl CommandArgs {
|
||||||
pub async fn evaluate_once(
|
pub async fn evaluate_once(self) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||||
self,
|
let ctx = EvaluationContext::from_args(&self);
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> 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();
|
||||||
let shell_manager = self.shell_manager.clone();
|
let shell_manager = self.shell_manager.clone();
|
||||||
let input = self.input;
|
let input = self.input;
|
||||||
let call_info = self.call_info.evaluate(registry).await?;
|
let call_info = self.call_info.evaluate(&ctx).await?;
|
||||||
|
let scope = self.scope.clone();
|
||||||
|
|
||||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||||
host,
|
host,
|
||||||
@@ -109,39 +92,12 @@ impl CommandArgs {
|
|||||||
shell_manager,
|
shell_manager,
|
||||||
call_info,
|
call_info,
|
||||||
input,
|
input,
|
||||||
|
scope,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn evaluate_once_with_scope(
|
pub async fn process<'de, T: Deserialize<'de>>(self) -> Result<(T, InputStream), ShellError> {
|
||||||
self,
|
let args = self.evaluate_once().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
scope: &Scope,
|
|
||||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
|
||||||
let host = self.host.clone();
|
|
||||||
let ctrl_c = self.ctrl_c.clone();
|
|
||||||
let shell_manager = self.shell_manager.clone();
|
|
||||||
let input = self.input;
|
|
||||||
let call_info = UnevaluatedCallInfo {
|
|
||||||
name_tag: self.call_info.name_tag,
|
|
||||||
args: self.call_info.args,
|
|
||||||
scope: scope.clone(),
|
|
||||||
};
|
|
||||||
let call_info = call_info.evaluate(registry).await?;
|
|
||||||
|
|
||||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
|
||||||
host,
|
|
||||||
ctrl_c,
|
|
||||||
shell_manager,
|
|
||||||
call_info,
|
|
||||||
input,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn process<'de, T: Deserialize<'de>>(
|
|
||||||
self,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<(T, InputStream), ShellError> {
|
|
||||||
let args = self.evaluate_once(registry).await?;
|
|
||||||
let call_info = args.call_info.clone();
|
let call_info = args.call_info.clone();
|
||||||
|
|
||||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||||
@@ -156,14 +112,13 @@ pub struct RunnableContext {
|
|||||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||||
pub ctrl_c: Arc<AtomicBool>,
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||||
pub registry: CommandRegistry,
|
pub scope: Scope,
|
||||||
pub name: Tag,
|
pub name: Tag,
|
||||||
pub raw_input: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableContext {
|
impl RunnableContext {
|
||||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||||
self.registry.get_command(name)
|
self.scope.get_command(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +141,7 @@ impl EvaluatedWholeStreamCommandArgs {
|
|||||||
shell_manager: ShellManager,
|
shell_manager: ShellManager,
|
||||||
call_info: CallInfo,
|
call_info: CallInfo,
|
||||||
input: impl Into<InputStream>,
|
input: impl Into<InputStream>,
|
||||||
|
scope: Scope,
|
||||||
) -> EvaluatedWholeStreamCommandArgs {
|
) -> EvaluatedWholeStreamCommandArgs {
|
||||||
EvaluatedWholeStreamCommandArgs {
|
EvaluatedWholeStreamCommandArgs {
|
||||||
args: EvaluatedCommandArgs {
|
args: EvaluatedCommandArgs {
|
||||||
@@ -193,6 +149,7 @@ impl EvaluatedWholeStreamCommandArgs {
|
|||||||
ctrl_c,
|
ctrl_c,
|
||||||
shell_manager,
|
shell_manager,
|
||||||
call_info,
|
call_info,
|
||||||
|
scope,
|
||||||
},
|
},
|
||||||
input: input.into(),
|
input: input.into(),
|
||||||
}
|
}
|
||||||
@@ -215,37 +172,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 {
|
||||||
@@ -253,6 +179,7 @@ pub struct EvaluatedCommandArgs {
|
|||||||
pub ctrl_c: Arc<AtomicBool>,
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
pub shell_manager: ShellManager,
|
pub shell_manager: ShellManager,
|
||||||
pub call_info: CallInfo,
|
pub call_info: CallInfo,
|
||||||
|
pub scope: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvaluatedCommandArgs {
|
impl EvaluatedCommandArgs {
|
||||||
@@ -262,10 +189,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> {
|
||||||
@@ -293,21 +220,113 @@ pub trait WholeStreamCommand: Send + Sync {
|
|||||||
|
|
||||||
fn usage(&self) -> &str;
|
fn usage(&self) -> &str;
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError>;
|
|
||||||
|
|
||||||
fn is_binary(&self) -> bool {
|
fn is_binary(&self) -> bool {
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom commands are blocks, so we can use the information in the block to also
|
||||||
|
// implement a WholeStreamCommand
|
||||||
|
#[allow(clippy::suspicious_else_formatting)]
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Block {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.params.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let call_info = args.call_info.clone();
|
||||||
|
|
||||||
|
let mut block = self.clone();
|
||||||
|
block.set_redirect(call_info.args.external_redirection);
|
||||||
|
|
||||||
|
let ctx = EvaluationContext::from_args(&args);
|
||||||
|
let evaluated = call_info.evaluate(&ctx).await?;
|
||||||
|
|
||||||
|
let input = args.input;
|
||||||
|
ctx.scope.enter_scope();
|
||||||
|
if let Some(args) = evaluated.args.positional {
|
||||||
|
// FIXME: do not do this
|
||||||
|
for arg in args.into_iter().zip(self.params.positional.iter()) {
|
||||||
|
let name = arg.1 .0.name();
|
||||||
|
|
||||||
|
if name.starts_with('$') {
|
||||||
|
ctx.scope.add_var(name, arg.0);
|
||||||
|
} else {
|
||||||
|
ctx.scope.add_var(format!("${}", name), arg.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(args) = evaluated.args.named {
|
||||||
|
for named in &block.params.named {
|
||||||
|
let name = named.0;
|
||||||
|
if let Some(value) = args.get(name) {
|
||||||
|
if name.starts_with('$') {
|
||||||
|
ctx.scope.add_var(name, value.clone());
|
||||||
|
} else {
|
||||||
|
ctx.scope.add_var(format!("${}", name), value.clone());
|
||||||
|
}
|
||||||
|
} else if name.starts_with('$') {
|
||||||
|
ctx.scope
|
||||||
|
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||||
|
} else {
|
||||||
|
ctx.scope.add_var(
|
||||||
|
format!("${}", name),
|
||||||
|
UntaggedValue::nothing().into_untagged_value(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for named in &block.params.named {
|
||||||
|
let name = named.0;
|
||||||
|
if name.starts_with('$') {
|
||||||
|
ctx.scope
|
||||||
|
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||||
|
} else {
|
||||||
|
ctx.scope.add_var(
|
||||||
|
format!("${}", name),
|
||||||
|
UntaggedValue::nothing().into_untagged_value(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = run_block(&block, &ctx, input).await;
|
||||||
|
ctx.scope.exit_scope();
|
||||||
|
result.map(|x| x.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_binary(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_internal(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||||
|
|
||||||
@@ -343,18 +362,18 @@ 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) -> 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();
|
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
UntaggedValue::string(get_help(&*cl, &args.scope)).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).await
|
||||||
Ok(stream) => stream,
|
|
||||||
Err(err) => OutputStream::one(Err(err)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,74 +381,15 @@ impl Command {
|
|||||||
self.0.is_binary()
|
self.0.is_binary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_internal(&self) -> bool {
|
||||||
|
self.0.is_internal()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||||
&*self.0
|
&*self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FnFilterCommand {
|
|
||||||
name: String,
|
|
||||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for FnFilterCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"usage"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
CommandArgs {
|
|
||||||
host,
|
|
||||||
ctrl_c,
|
|
||||||
shell_manager,
|
|
||||||
call_info,
|
|
||||||
input,
|
|
||||||
..
|
|
||||||
}: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = Arc::new(registry.clone());
|
|
||||||
let func = self.func;
|
|
||||||
|
|
||||||
Ok(input
|
|
||||||
.then(move |it| {
|
|
||||||
let host = host.clone();
|
|
||||||
let registry = registry.clone();
|
|
||||||
let ctrl_c = ctrl_c.clone();
|
|
||||||
let shell_manager = shell_manager.clone();
|
|
||||||
let call_info = call_info.clone();
|
|
||||||
async move {
|
|
||||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
|
||||||
Err(err) => {
|
|
||||||
return OutputStream::one(Err(err));
|
|
||||||
}
|
|
||||||
Ok(args) => args,
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = EvaluatedFilterCommandArgs::new(
|
|
||||||
host.clone(),
|
|
||||||
ctrl_c.clone(),
|
|
||||||
shell_manager.clone(),
|
|
||||||
call_info,
|
|
||||||
);
|
|
||||||
|
|
||||||
match func(args) {
|
|
||||||
Err(err) => return OutputStream::one(Err(err)),
|
|
||||||
Ok(stream) => stream,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||||
Command(Arc::new(command))
|
Command(Arc::new(command))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
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;
|
||||||
@@ -28,40 +27,21 @@ impl WholeStreamCommand for Compact {
|
|||||||
"Creates a table with non-empty rows"
|
"Creates a table with non-empty rows"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
compact(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
compact(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
},
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn compact(
|
pub async fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let (CompactArgs { rest: columns }, input) = args.process().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (CompactArgs { rest: columns }, input) = args.process(®istry).await?;
|
|
||||||
Ok(input
|
Ok(input
|
||||||
.filter_map(move |item| {
|
.filter_map(move |item| {
|
||||||
future::ready(if columns.is_empty() {
|
future::ready(if columns.is_empty() {
|
||||||
@@ -95,11 +75,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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
crates/nu-cli/src/commands/config/clear.rs
Normal file
49
crates/nu-cli/src/commands/config/clear.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
clear(args).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) -> 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),
|
||||||
|
)))
|
||||||
|
}
|
||||||
33
crates/nu-cli/src/commands/config/command.rs
Normal file
33
crates/nu-cli/src/commands/config/command.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{CommandArgs, 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) -> 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
68
crates/nu-cli/src/commands/config/get.rs
Normal file
68
crates/nu-cli/src/commands/config/get.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
get(args).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) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
|
let (GetArgs { path }, _) = args.process().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)),
|
||||||
|
})
|
||||||
|
}
|
||||||
51
crates/nu-cli/src/commands/config/load.rs
Normal file
51
crates/nu-cli/src/commands/config/load.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
set(args).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(args: CommandArgs) -> 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().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;
|
||||||
41
crates/nu-cli/src/commands/config/path.rs
Normal file
41
crates/nu-cli/src/commands/config/path.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
path(args).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) -> 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),
|
||||||
|
)))
|
||||||
|
}
|
||||||
67
crates/nu-cli/src/commands/config/remove.rs
Normal file
67
crates/nu-cli/src/commands/config/remove.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
remove(args).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) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
let (RemoveArgs { remove }, _) = args.process().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(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
89
crates/nu-cli/src/commands/config/set.rs
Normal file
89
crates/nu-cli/src/commands/config/set.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
set(args).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) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
|
let (SetArgs { path, mut value }, _) = args.process().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),
|
||||||
|
}
|
||||||
|
}
|
||||||
90
crates/nu-cli/src/commands/config/set_into.rs
Normal file
90
crates/nu-cli/src/commands/config/set_into.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
set_into(args).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) -> 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().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,4 @@
|
|||||||
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 +6,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,43 +18,69 @@ 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 {
|
||||||
"Show the total number of rows or items."
|
"Show the total number of rows or items."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
let tag = args.call_info.name_tag.clone();
|
||||||
args: CommandArgs,
|
let (CountArgs { column }, input) = args.process().await?;
|
||||||
_registry: &CommandRegistry,
|
let rows: Vec<Value> = input.collect().await;
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let name = args.call_info.name_tag.clone();
|
|
||||||
let rows: Vec<Value> = args.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,4 @@
|
|||||||
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};
|
||||||
@@ -36,14 +35,10 @@ impl WholeStreamCommand for Cpy {
|
|||||||
"Copy files."
|
"Copy files."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let shell_manager = args.shell_manager.clone();
|
let shell_manager = args.shell_manager.clone();
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let (args, _) = args.process(®istry).await?;
|
let (args, _) = args.process().await?;
|
||||||
shell_manager.cp(args, name)
|
shell_manager.cp(args, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +61,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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
crates/nu-cli/src/commands/date/command.rs
Normal file
41
crates/nu-cli/src/commands/date/command.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Command, &args.scope))
|
||||||
|
.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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
109
crates/nu-cli/src/commands/date/format.rs
Normal file
109
crates/nu-cli/src/commands/date/format.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
Dictionary, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use std::fmt::{self, write};
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct FormatArgs {
|
||||||
|
format: Tagged<String>,
|
||||||
|
table: 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("table", "print date in a table", Some('t'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Format a given date using the given format string."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
format(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Format the current date",
|
||||||
|
example: "date now | date format '%Y.%m.%d_%H %M %S,%z'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Format the current date and print in a table",
|
||||||
|
example: "date now | date format -t '%Y-%m-%d_%H:%M:%S %z'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn format(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let (FormatArgs { format, table }, input) = args.process().await?;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut output = String::new();
|
||||||
|
if let Err(fmt::Error) =
|
||||||
|
write(&mut output, format_args!("{}", dt.format(&format.item)))
|
||||||
|
{
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"The date format is invalid",
|
||||||
|
"invalid strftime format",
|
||||||
|
&format.tag,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let value = if table {
|
||||||
|
let mut indexmap = IndexMap::new();
|
||||||
|
indexmap.insert(
|
||||||
|
"formatted".to_string(),
|
||||||
|
UntaggedValue::string(&output).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||||
|
} else {
|
||||||
|
UntaggedValue::string(&output).into_value(&tag)
|
||||||
|
};
|
||||||
|
|
||||||
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
75
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
75
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono_tz::TZ_VARIANTS;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date list-timezone"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date list-timezone")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"List supported time zones."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
list_timezone(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "List all supported time zones",
|
||||||
|
example: "date list-timezone",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "List all supported European time zones",
|
||||||
|
example: "date list-timezone | where timezone =~ Europe",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once().await?;
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let list = TZ_VARIANTS.iter().map(move |tz| {
|
||||||
|
let mut entries = IndexMap::new();
|
||||||
|
|
||||||
|
entries.insert(
|
||||||
|
"timezone".to_string(),
|
||||||
|
UntaggedValue::string(tz.name()).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(ReturnSuccess::Value(
|
||||||
|
UntaggedValue::Row(Dictionary { entries }).into_value(&tag),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(list).to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
crates/nu-cli/src/commands/date/mod.rs
Normal file
15
crates/nu-cli/src/commands/date/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
pub mod command;
|
||||||
|
pub mod format;
|
||||||
|
pub mod list_timezone;
|
||||||
|
pub mod now;
|
||||||
|
pub mod to_table;
|
||||||
|
pub mod to_timezone;
|
||||||
|
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
pub use command::Command as Date;
|
||||||
|
pub use format::Date as DateFormat;
|
||||||
|
pub use list_timezone::Date as DateListTimeZone;
|
||||||
|
pub use now::Date as DateNow;
|
||||||
|
pub use to_table::Date as DateToTable;
|
||||||
|
pub use to_timezone::Date as DateToTimeZone;
|
||||||
50
crates/nu-cli/src/commands/date/now.rs
Normal file
50
crates/nu-cli/src/commands/date/now.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, UntaggedValue};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
"Get the current date."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
now(args).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn now(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once().await?;
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let now: DateTime<Local> = Local::now();
|
||||||
|
|
||||||
|
let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
|
||||||
|
|
||||||
|
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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Modified from chrono::format::scan
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, Offset, TimeZone};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
use titlecase::titlecase;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
|
pub enum ParseErrorKind {
|
||||||
|
/// Given field is out of permitted range.
|
||||||
|
OutOfRange,
|
||||||
|
|
||||||
|
/// The input string has some invalid character sequence for given formatting items.
|
||||||
|
Invalid,
|
||||||
|
|
||||||
|
/// The input string has been prematurely ended.
|
||||||
|
TooShort,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn datetime_in_timezone(
|
||||||
|
dt: &DateTime<FixedOffset>,
|
||||||
|
s: &str,
|
||||||
|
) -> Result<DateTime<FixedOffset>, ParseErrorKind> {
|
||||||
|
match timezone_offset_internal(s, true, true) {
|
||||||
|
Ok(offset) => match FixedOffset::east_opt(offset) {
|
||||||
|
Some(offset) => Ok(dt.with_timezone(&offset)),
|
||||||
|
None => Err(ParseErrorKind::OutOfRange),
|
||||||
|
},
|
||||||
|
Err(ParseErrorKind::Invalid) => {
|
||||||
|
if s.to_lowercase() == "local" {
|
||||||
|
Ok(dt.with_timezone(Local::now().offset()))
|
||||||
|
} else {
|
||||||
|
let tz: Tz = parse_timezone_internal(s)?;
|
||||||
|
let offset = tz.offset_from_utc_datetime(&dt.naive_utc()).fix();
|
||||||
|
Ok(dt.with_timezone(&offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_timezone_internal(s: &str) -> Result<Tz, ParseErrorKind> {
|
||||||
|
if let Ok(tz) = s.parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else if let Ok(tz) = titlecase(s).parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else if let Ok(tz) = s.to_uppercase().parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else {
|
||||||
|
Err(ParseErrorKind::Invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timezone_offset_internal(
|
||||||
|
mut s: &str,
|
||||||
|
consume_colon: bool,
|
||||||
|
allow_missing_minutes: bool,
|
||||||
|
) -> Result<i32, ParseErrorKind> {
|
||||||
|
fn digits(s: &str) -> Result<(u8, u8), ParseErrorKind> {
|
||||||
|
let b = s.as_bytes();
|
||||||
|
if b.len() < 2 {
|
||||||
|
Err(ParseErrorKind::TooShort)
|
||||||
|
} else {
|
||||||
|
Ok((b[0], b[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let negative = match s.as_bytes().first() {
|
||||||
|
Some(&b'+') => false,
|
||||||
|
Some(&b'-') => true,
|
||||||
|
Some(_) => return Err(ParseErrorKind::Invalid),
|
||||||
|
None => return Err(ParseErrorKind::TooShort),
|
||||||
|
};
|
||||||
|
s = &s[1..];
|
||||||
|
|
||||||
|
// hours (00--99)
|
||||||
|
let hours = match digits(s)? {
|
||||||
|
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
|
||||||
|
_ => return Err(ParseErrorKind::Invalid),
|
||||||
|
};
|
||||||
|
s = &s[2..];
|
||||||
|
|
||||||
|
// colons (and possibly other separators)
|
||||||
|
if consume_colon {
|
||||||
|
s = s.trim_start_matches(|c: char| c == ':' || c.is_whitespace());
|
||||||
|
}
|
||||||
|
|
||||||
|
// minutes (00--59)
|
||||||
|
// if the next two items are digits then we have to add minutes
|
||||||
|
let minutes = if let Ok(ds) = digits(s) {
|
||||||
|
match ds {
|
||||||
|
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
|
||||||
|
(b'6'..=b'9', b'0'..=b'9') => return Err(ParseErrorKind::OutOfRange),
|
||||||
|
_ => return Err(ParseErrorKind::Invalid),
|
||||||
|
}
|
||||||
|
} else if allow_missing_minutes {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
return Err(ParseErrorKind::TooShort);
|
||||||
|
};
|
||||||
|
match s.len() {
|
||||||
|
len if len >= 2 => &s[2..],
|
||||||
|
len if len == 0 => s,
|
||||||
|
_ => return Err(ParseErrorKind::TooShort),
|
||||||
|
};
|
||||||
|
|
||||||
|
let seconds = hours * 3600 + minutes * 60;
|
||||||
|
Ok(if negative { -seconds } else { seconds })
|
||||||
|
}
|
||||||
105
crates/nu-cli/src/commands/date/to_table.rs
Normal file
105
crates/nu-cli/src/commands/date/to_table.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date to-table"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date to-table")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Print the date in a structured table."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
to_table(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Print the current date in a table",
|
||||||
|
example: "date now | date to-table",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn to_table(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once().await?;
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let input = args.input;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
|
||||||
|
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||||
|
|
||||||
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
110
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
110
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use crate::commands::date::parser::{datetime_in_timezone, ParseErrorKind};
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DateToTimeZoneArgs {
|
||||||
|
timezone: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date to-timezone"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date to-timezone").required(
|
||||||
|
"time zone",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"time zone description",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert a date to a given time zone.
|
||||||
|
|
||||||
|
Use `date list-timezone` to list all supported time zones.
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
to_timezone(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get the current date in UTC+05:00",
|
||||||
|
example: "date now | date to-timezone +0500",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the current local date",
|
||||||
|
example: "date now | date to-timezone local",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the current date in Hawaii",
|
||||||
|
example: "date now | date to-timezone US/Hawaii",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn to_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let (DateToTimeZoneArgs { timezone }, input) = args.process().await?;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => match datetime_in_timezone(&dt, &timezone.item) {
|
||||||
|
Ok(dt) => {
|
||||||
|
let value = UntaggedValue::date(dt).into_value(&tag);
|
||||||
|
|
||||||
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
Err(e) => Err(ShellError::labeled_error(
|
||||||
|
error_message(e),
|
||||||
|
"invalid time zone",
|
||||||
|
&timezone.tag,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_message(err: ParseErrorKind) -> &'static str {
|
||||||
|
match err {
|
||||||
|
ParseErrorKind::Invalid => "The time zone description is invalid",
|
||||||
|
ParseErrorKind::OutOfRange => "The time zone offset is out of range",
|
||||||
|
ParseErrorKind::TooShort => "The format of the time zone is invalid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
crates/nu-cli/src/commands/date/utc.rs
Normal file
55
crates/nu-cli/src/commands/date/utc.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
utc(args).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn utc(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once().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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,21 +24,13 @@ impl WholeStreamCommand for Debug {
|
|||||||
"Print the Rust debug representation of the values"
|
"Print the Rust debug representation of the values"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
debug_value(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
debug_value(args, registry).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn debug_value(
|
async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let (DebugArgs { raw }, input) = args.process().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (DebugArgs { raw }, input) = args.process(®istry).await?;
|
|
||||||
Ok(input
|
Ok(input
|
||||||
.map(move |v| {
|
.map(move |v| {
|
||||||
if raw {
|
if raw {
|
||||||
@@ -55,11 +47,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
crates/nu-cli/src/commands/def.rs
Normal file
48
crates/nu-cli/src/commands/def.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{hir::CapturedBlock, Signature, SyntaxShape, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Def;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DefArgs {
|
||||||
|
pub name: Tagged<String>,
|
||||||
|
pub args: Tagged<Vec<Value>>,
|
||||||
|
pub block: CapturedBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Def {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"def"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("def")
|
||||||
|
.required("name", SyntaxShape::String, "the name of the command")
|
||||||
|
.required(
|
||||||
|
"params",
|
||||||
|
SyntaxShape::Table,
|
||||||
|
"the parameters of the command",
|
||||||
|
)
|
||||||
|
.required("block", SyntaxShape::Block, "the body of the command")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create a command and set it to a definition."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, _args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
// Currently, we don't do anything here because we should have already
|
||||||
|
// installed the definition as we entered the scope
|
||||||
|
// We just create a command so that we can get proper coloring
|
||||||
|
Ok(OutputStream::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
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};
|
||||||
@@ -34,39 +33,31 @@ impl WholeStreamCommand for Default {
|
|||||||
"Sets a default row's column if missing."
|
"Sets a default row's column if missing."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
default(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
default(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn default(
|
async fn default(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let (DefaultArgs { column, value }, input) = args.process().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (DefaultArgs { column, value }, input) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
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 +74,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
248
crates/nu-cli/src/commands/default_context.rs
Normal file
248
crates/nu-cli/src/commands/default_context.rs
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||||
|
let context = EvaluationContext::basic()?;
|
||||||
|
|
||||||
|
{
|
||||||
|
use crate::commands::*;
|
||||||
|
|
||||||
|
context.add_commands(vec![
|
||||||
|
// Fundamentals
|
||||||
|
whole_stream_command(NuPlugin),
|
||||||
|
whole_stream_command(Let),
|
||||||
|
whole_stream_command(LetEnv),
|
||||||
|
whole_stream_command(Def),
|
||||||
|
whole_stream_command(Source),
|
||||||
|
// System/file operations
|
||||||
|
whole_stream_command(Exec),
|
||||||
|
whole_stream_command(Pwd),
|
||||||
|
whole_stream_command(Ls),
|
||||||
|
whole_stream_command(Du),
|
||||||
|
whole_stream_command(Cd),
|
||||||
|
whole_stream_command(Remove),
|
||||||
|
whole_stream_command(Open),
|
||||||
|
whole_stream_command(Config),
|
||||||
|
whole_stream_command(ConfigGet),
|
||||||
|
whole_stream_command(ConfigSet),
|
||||||
|
whole_stream_command(ConfigSetInto),
|
||||||
|
whole_stream_command(ConfigClear),
|
||||||
|
whole_stream_command(ConfigLoad),
|
||||||
|
whole_stream_command(ConfigRemove),
|
||||||
|
whole_stream_command(ConfigPath),
|
||||||
|
whole_stream_command(Help),
|
||||||
|
whole_stream_command(History),
|
||||||
|
whole_stream_command(Save),
|
||||||
|
whole_stream_command(Touch),
|
||||||
|
whole_stream_command(Cpy),
|
||||||
|
whole_stream_command(Date),
|
||||||
|
whole_stream_command(DateListTimeZone),
|
||||||
|
whole_stream_command(DateNow),
|
||||||
|
whole_stream_command(DateToTable),
|
||||||
|
whole_stream_command(DateToTimeZone),
|
||||||
|
whole_stream_command(DateFormat),
|
||||||
|
whole_stream_command(Cal),
|
||||||
|
whole_stream_command(Mkdir),
|
||||||
|
whole_stream_command(Mv),
|
||||||
|
whole_stream_command(Kill),
|
||||||
|
whole_stream_command(Version),
|
||||||
|
whole_stream_command(Clear),
|
||||||
|
whole_stream_command(Describe),
|
||||||
|
whole_stream_command(Which),
|
||||||
|
whole_stream_command(Debug),
|
||||||
|
whole_stream_command(WithEnv),
|
||||||
|
whole_stream_command(Do),
|
||||||
|
whole_stream_command(Sleep),
|
||||||
|
// Statistics
|
||||||
|
whole_stream_command(Size),
|
||||||
|
whole_stream_command(Count),
|
||||||
|
whole_stream_command(Benchmark),
|
||||||
|
// Metadata
|
||||||
|
whole_stream_command(Tags),
|
||||||
|
// Shells
|
||||||
|
whole_stream_command(Next),
|
||||||
|
whole_stream_command(Previous),
|
||||||
|
whole_stream_command(Shells),
|
||||||
|
whole_stream_command(Enter),
|
||||||
|
whole_stream_command(Exit),
|
||||||
|
// Viz
|
||||||
|
whole_stream_command(Chart),
|
||||||
|
// Viewers
|
||||||
|
whole_stream_command(Autoview),
|
||||||
|
whole_stream_command(Table),
|
||||||
|
// Text manipulation
|
||||||
|
whole_stream_command(Hash),
|
||||||
|
whole_stream_command(HashBase64),
|
||||||
|
whole_stream_command(Split),
|
||||||
|
whole_stream_command(SplitColumn),
|
||||||
|
whole_stream_command(SplitRow),
|
||||||
|
whole_stream_command(SplitChars),
|
||||||
|
whole_stream_command(Lines),
|
||||||
|
whole_stream_command(Echo),
|
||||||
|
whole_stream_command(Parse),
|
||||||
|
whole_stream_command(Str),
|
||||||
|
whole_stream_command(StrToDecimal),
|
||||||
|
whole_stream_command(StrToInteger),
|
||||||
|
whole_stream_command(StrDowncase),
|
||||||
|
whole_stream_command(StrUpcase),
|
||||||
|
whole_stream_command(StrCapitalize),
|
||||||
|
whole_stream_command(StrFindReplace),
|
||||||
|
whole_stream_command(StrFrom),
|
||||||
|
whole_stream_command(StrSubstring),
|
||||||
|
whole_stream_command(StrSet),
|
||||||
|
whole_stream_command(StrToDatetime),
|
||||||
|
whole_stream_command(StrContains),
|
||||||
|
whole_stream_command(StrIndexOf),
|
||||||
|
whole_stream_command(StrTrim),
|
||||||
|
whole_stream_command(StrTrimLeft),
|
||||||
|
whole_stream_command(StrTrimRight),
|
||||||
|
whole_stream_command(StrStartsWith),
|
||||||
|
whole_stream_command(StrEndsWith),
|
||||||
|
whole_stream_command(StrCollect),
|
||||||
|
whole_stream_command(StrLength),
|
||||||
|
whole_stream_command(StrLPad),
|
||||||
|
whole_stream_command(StrReverse),
|
||||||
|
whole_stream_command(StrRPad),
|
||||||
|
whole_stream_command(StrCamelCase),
|
||||||
|
whole_stream_command(StrPascalCase),
|
||||||
|
whole_stream_command(StrKebabCase),
|
||||||
|
whole_stream_command(StrSnakeCase),
|
||||||
|
whole_stream_command(StrScreamingSnakeCase),
|
||||||
|
whole_stream_command(BuildString),
|
||||||
|
whole_stream_command(Ansi),
|
||||||
|
whole_stream_command(Char),
|
||||||
|
// Column manipulation
|
||||||
|
whole_stream_command(Move),
|
||||||
|
whole_stream_command(Reject),
|
||||||
|
whole_stream_command(Select),
|
||||||
|
whole_stream_command(Get),
|
||||||
|
whole_stream_command(Update),
|
||||||
|
whole_stream_command(Insert),
|
||||||
|
whole_stream_command(IntoInt),
|
||||||
|
whole_stream_command(SplitBy),
|
||||||
|
// Row manipulation
|
||||||
|
whole_stream_command(Reverse),
|
||||||
|
whole_stream_command(Append),
|
||||||
|
whole_stream_command(Prepend),
|
||||||
|
whole_stream_command(SortBy),
|
||||||
|
whole_stream_command(GroupBy),
|
||||||
|
whole_stream_command(GroupByDate),
|
||||||
|
whole_stream_command(First),
|
||||||
|
whole_stream_command(Last),
|
||||||
|
whole_stream_command(Every),
|
||||||
|
whole_stream_command(Nth),
|
||||||
|
whole_stream_command(Drop),
|
||||||
|
whole_stream_command(Format),
|
||||||
|
whole_stream_command(FileSize),
|
||||||
|
whole_stream_command(Where),
|
||||||
|
whole_stream_command(If),
|
||||||
|
whole_stream_command(Compact),
|
||||||
|
whole_stream_command(Default),
|
||||||
|
whole_stream_command(Skip),
|
||||||
|
whole_stream_command(SkipUntil),
|
||||||
|
whole_stream_command(SkipWhile),
|
||||||
|
whole_stream_command(Keep),
|
||||||
|
whole_stream_command(KeepUntil),
|
||||||
|
whole_stream_command(KeepWhile),
|
||||||
|
whole_stream_command(Range),
|
||||||
|
whole_stream_command(Rename),
|
||||||
|
whole_stream_command(Uniq),
|
||||||
|
whole_stream_command(Each),
|
||||||
|
whole_stream_command(EachGroup),
|
||||||
|
whole_stream_command(EachWindow),
|
||||||
|
whole_stream_command(Empty),
|
||||||
|
// Table manipulation
|
||||||
|
whole_stream_command(Flatten),
|
||||||
|
whole_stream_command(Move),
|
||||||
|
whole_stream_command(Merge),
|
||||||
|
whole_stream_command(Shuffle),
|
||||||
|
whole_stream_command(Wrap),
|
||||||
|
whole_stream_command(Pivot),
|
||||||
|
whole_stream_command(Headers),
|
||||||
|
whole_stream_command(Reduce),
|
||||||
|
// Data processing
|
||||||
|
whole_stream_command(Histogram),
|
||||||
|
whole_stream_command(Autoenv),
|
||||||
|
whole_stream_command(AutoenvTrust),
|
||||||
|
whole_stream_command(AutoenvUnTrust),
|
||||||
|
whole_stream_command(Math),
|
||||||
|
whole_stream_command(MathAbs),
|
||||||
|
whole_stream_command(MathAverage),
|
||||||
|
whole_stream_command(MathEval),
|
||||||
|
whole_stream_command(MathMedian),
|
||||||
|
whole_stream_command(MathMinimum),
|
||||||
|
whole_stream_command(MathMode),
|
||||||
|
whole_stream_command(MathMaximum),
|
||||||
|
whole_stream_command(MathStddev),
|
||||||
|
whole_stream_command(MathSummation),
|
||||||
|
whole_stream_command(MathVariance),
|
||||||
|
whole_stream_command(MathProduct),
|
||||||
|
whole_stream_command(MathRound),
|
||||||
|
whole_stream_command(MathFloor),
|
||||||
|
whole_stream_command(MathCeil),
|
||||||
|
// File format output
|
||||||
|
whole_stream_command(To),
|
||||||
|
whole_stream_command(ToCSV),
|
||||||
|
whole_stream_command(ToHTML),
|
||||||
|
whole_stream_command(ToJSON),
|
||||||
|
whole_stream_command(ToMarkdown),
|
||||||
|
whole_stream_command(ToTOML),
|
||||||
|
whole_stream_command(ToTSV),
|
||||||
|
whole_stream_command(ToURL),
|
||||||
|
whole_stream_command(ToYAML),
|
||||||
|
whole_stream_command(ToXML),
|
||||||
|
// File format input
|
||||||
|
whole_stream_command(From),
|
||||||
|
whole_stream_command(FromCSV),
|
||||||
|
whole_stream_command(FromEML),
|
||||||
|
whole_stream_command(FromTSV),
|
||||||
|
whole_stream_command(FromSSV),
|
||||||
|
whole_stream_command(FromINI),
|
||||||
|
whole_stream_command(FromJSON),
|
||||||
|
whole_stream_command(FromODS),
|
||||||
|
whole_stream_command(FromTOML),
|
||||||
|
whole_stream_command(FromURL),
|
||||||
|
whole_stream_command(FromXLSX),
|
||||||
|
whole_stream_command(FromXML),
|
||||||
|
whole_stream_command(FromYAML),
|
||||||
|
whole_stream_command(FromYML),
|
||||||
|
whole_stream_command(FromIcs),
|
||||||
|
whole_stream_command(FromVcf),
|
||||||
|
// "Private" commands (not intended to be accessed directly)
|
||||||
|
whole_stream_command(RunExternalCommand { interactive }),
|
||||||
|
// Random value generation
|
||||||
|
whole_stream_command(Random),
|
||||||
|
whole_stream_command(RandomBool),
|
||||||
|
whole_stream_command(RandomDice),
|
||||||
|
#[cfg(feature = "uuid_crate")]
|
||||||
|
whole_stream_command(RandomUUID),
|
||||||
|
whole_stream_command(RandomInteger),
|
||||||
|
whole_stream_command(RandomDecimal),
|
||||||
|
whole_stream_command(RandomChars),
|
||||||
|
// Path
|
||||||
|
whole_stream_command(PathBasename),
|
||||||
|
whole_stream_command(PathCommand),
|
||||||
|
whole_stream_command(PathDirname),
|
||||||
|
whole_stream_command(PathExists),
|
||||||
|
whole_stream_command(PathExpand),
|
||||||
|
whole_stream_command(PathExtension),
|
||||||
|
whole_stream_command(PathFilestem),
|
||||||
|
whole_stream_command(PathType),
|
||||||
|
// Url
|
||||||
|
whole_stream_command(UrlCommand),
|
||||||
|
whole_stream_command(UrlScheme),
|
||||||
|
whole_stream_command(UrlPath),
|
||||||
|
whole_stream_command(UrlHost),
|
||||||
|
whole_stream_command(UrlQuery),
|
||||||
|
whole_stream_command(Seq),
|
||||||
|
whole_stream_command(SeqDates),
|
||||||
|
]);
|
||||||
|
|
||||||
|
#[cfg(feature = "clipboard-cli")]
|
||||||
|
{
|
||||||
|
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(context)
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@ use crate::prelude::*;
|
|||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
pub struct What;
|
pub struct Describe;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct WhatArgs {}
|
pub struct DescribeArgs {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for What {
|
impl WholeStreamCommand for Describe {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"describe"
|
"describe"
|
||||||
}
|
}
|
||||||
@@ -23,19 +23,12 @@ impl WholeStreamCommand for What {
|
|||||||
"Describes the objects in the stream."
|
"Describes the objects in the stream."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
describe(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
what(args, registry).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn what(
|
pub async fn describe(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
|
||||||
_registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
Ok(args
|
Ok(args
|
||||||
.input
|
.input
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
@@ -49,12 +42,13 @@ pub async fn what(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::What;
|
use super::Describe;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(What {})
|
Ok(test_examples(Describe {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
118
crates/nu-cli/src/commands/do_.rs
Normal file
118
crates/nu-cli/src/commands/do_.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Do;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct DoArgs {
|
||||||
|
block: CapturedBlock,
|
||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
do_(args).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![]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||||
|
|
||||||
|
let context = EvaluationContext::from_raw(&raw_args);
|
||||||
|
let (
|
||||||
|
DoArgs {
|
||||||
|
ignore_errors,
|
||||||
|
mut block,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = raw_args.process().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.block.set_redirect(block_redirection);
|
||||||
|
context.scope.enter_scope();
|
||||||
|
let result = run_block(&block.block, &context, input).await;
|
||||||
|
context.scope.exit_scope();
|
||||||
|
|
||||||
|
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::empty()),
|
||||||
|
}
|
||||||
|
} 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,4 @@
|
|||||||
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,33 +21,16 @@ 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(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
drop(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let (DropArgs { rows }, input) = args.process(®istry).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 +52,40 @@ impl WholeStreamCommand for Drop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DropArgs { rows }, input) = args.process().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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,12 +73,8 @@ impl WholeStreamCommand for Du {
|
|||||||
"Find disk usage sizes of specified items"
|
"Find disk usage sizes of specified items"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
du(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
du(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -90,13 +86,12 @@ impl WholeStreamCommand for Du {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn du(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn du(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let ctrl_c = args.ctrl_c.clone();
|
let ctrl_c = args.ctrl_c.clone();
|
||||||
let ctrl_c_copy = ctrl_c.clone();
|
let ctrl_c_copy = ctrl_c.clone();
|
||||||
|
|
||||||
let (args, _): (DuArgs, _) = args.process(®istry).await?;
|
let (args, _): (DuArgs, _) = args.process().await?;
|
||||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||||
Pattern::new(&x.item)
|
Pattern::new(&x.item)
|
||||||
.map(Option::Some)
|
.map(Option::Some)
|
||||||
@@ -343,7 +338,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 +347,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 +371,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 +389,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 | math 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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
165
crates/nu-cli/src/commands/each/command.rs
Normal file
165
crates/nu-cli/src/commands/each/command.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
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::CapturedBlock, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Each;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct EachArgs {
|
||||||
|
block: CapturedBlock,
|
||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
each(args).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")]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_row(
|
||||||
|
captured_block: Arc<Box<CapturedBlock>>,
|
||||||
|
context: Arc<EvaluationContext>,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let input_clone = input.clone();
|
||||||
|
// When we process a row, we need to know whether the block wants to have the contents of the row as
|
||||||
|
// a parameter to the block (so it gets assigned to a variable that can be used inside the block) or
|
||||||
|
// if it wants the contents as as an input stream
|
||||||
|
|
||||||
|
let input_stream = if !captured_block.block.params.positional.is_empty() {
|
||||||
|
InputStream::empty()
|
||||||
|
} else {
|
||||||
|
once(async { Ok(input_clone) }).to_input_stream()
|
||||||
|
};
|
||||||
|
|
||||||
|
context.scope.enter_scope();
|
||||||
|
context.scope.add_vars(&captured_block.captured.entries);
|
||||||
|
|
||||||
|
if !captured_block.block.params.positional.is_empty() {
|
||||||
|
// FIXME: add check for more than parameter, once that's supported
|
||||||
|
context
|
||||||
|
.scope
|
||||||
|
.add_var(captured_block.block.params.positional[0].0.name(), input);
|
||||||
|
} else {
|
||||||
|
context.scope.add_var("$it", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run_block(&captured_block.block, &*context, input_stream).await;
|
||||||
|
|
||||||
|
context.scope.exit_scope();
|
||||||
|
|
||||||
|
Ok(result?.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) -> Result<OutputStream, ShellError> {
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||||
|
|
||||||
|
let (each_args, input): (EachArgs, _) = raw_args.process().await?;
|
||||||
|
let block = Arc::new(Box::new(each_args.block));
|
||||||
|
|
||||||
|
if each_args.numbered.item {
|
||||||
|
Ok(input
|
||||||
|
.enumerate()
|
||||||
|
.then(move |input| {
|
||||||
|
let block = block.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let row = make_indexed_item(input.0, input.1);
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(block, 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 context = context.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(block, 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 {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
116
crates/nu-cli/src/commands/each/group.rs
Normal file
116
crates/nu-cli/src/commands/each/group.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use crate::commands::each::process_row;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::CapturedBlock, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub struct EachGroup;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct EachGroupArgs {
|
||||||
|
group_size: Tagged<usize>,
|
||||||
|
block: CapturedBlock,
|
||||||
|
//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) -> Result<OutputStream, ShellError> {
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||||
|
let (each_args, input): (EachGroupArgs, _) = raw_args.process().await?;
|
||||||
|
let block = Arc::new(Box::new(each_args.block));
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.chunks(each_args.group_size.item)
|
||||||
|
.then(move |input| run_block_on_vec(input, block.clone(), context.clone()))
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_block_on_vec(
|
||||||
|
input: Vec<Value>,
|
||||||
|
block: Arc<Box<CapturedBlock>>,
|
||||||
|
context: Arc<EvaluationContext>,
|
||||||
|
) -> impl Future<Output = OutputStream> {
|
||||||
|
let value = Value {
|
||||||
|
value: UntaggedValue::Table(input),
|
||||||
|
tag: Tag::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(block, 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;
|
||||||
105
crates/nu-cli/src/commands/each/window.rs
Normal file
105
crates/nu-cli/src/commands/each/window.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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::CapturedBlock, Primitive, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub struct EachWindow;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct EachWindowArgs {
|
||||||
|
window_size: Tagged<usize>,
|
||||||
|
block: CapturedBlock,
|
||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||||
|
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process().await?;
|
||||||
|
let block = Arc::new(Box::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 context = context.clone();
|
||||||
|
let local_window = window.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
if i % stride == 0 {
|
||||||
|
Some(run_block_on_vec(local_window, block, 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,12 +3,12 @@ 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;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct EchoArgs {
|
pub struct EchoArgs {
|
||||||
pub rest: Vec<Value>,
|
pub rest: Vec<Value>,
|
||||||
}
|
}
|
||||||
@@ -27,12 +27,8 @@ impl WholeStreamCommand for Echo {
|
|||||||
"Echo the arguments back to the user."
|
"Echo the arguments back to the user."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
echo(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
echo(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -51,72 +47,122 @@ impl WholeStreamCommand for Echo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn echo(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let (args, _): (EchoArgs, _) = args.process().await?;
|
||||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
let stream = args.rest.into_iter().map(|i| {
|
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) => {
|
|
||||||
OutputStream::one(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))
|
||||||
futures::stream::iter(table.into_iter().map(ReturnSuccess::value)).to_output_stream()
|
.to_output_stream(),
|
||||||
}
|
|
||||||
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 output_vec = vec![];
|
x => OutputStream::one(Ok(ReturnSuccess::Value(x))),
|
||||||
|
|
||||||
let mut current = range.from.0.item;
|
|
||||||
while current != range.to.0.item {
|
|
||||||
output_vec.push(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(result) => match result {
|
|
||||||
UntaggedValue::Primitive(p) => p,
|
|
||||||
_ => {
|
|
||||||
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err((left_type, right_type)) => {
|
|
||||||
return OutputStream::one(Err(ShellError::coerce_error(
|
|
||||||
left_type.spanned(tag.span),
|
|
||||||
right_type.spanned(tag.span),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let RangeInclusion::Inclusive = range.to.1 {
|
|
||||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
|
|
||||||
}
|
|
||||||
|
|
||||||
futures::stream::iter(output_vec.into_iter()).to_output_stream()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
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 {
|
||||||
|
UntaggedValue::Primitive(p) => p,
|
||||||
|
_ => {
|
||||||
|
return Some(Err(ShellError::unimplemented(
|
||||||
|
"Internal error: expected a primitive result from increment",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err((left_type, right_type)) => {
|
||||||
|
return Some(Err(ShellError::coerce_error(
|
||||||
|
left_type.spanned(self.tag.span),
|
||||||
|
right_type.spanned(self.tag.span),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(ReturnSuccess::value(output))
|
||||||
|
} 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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
275
crates/nu-cli/src/commands/empty.rs
Normal file
275
crates/nu-cli/src/commands/empty.rs
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::CapturedBlock, ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||||
|
UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use nu_value_ext::{as_string, ValueExt};
|
||||||
|
|
||||||
|
use futures::stream::once;
|
||||||
|
|
||||||
|
#[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) -> Result<OutputStream, ShellError> {
|
||||||
|
is_empty(args).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) -> 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));
|
||||||
|
let (Arguments { rest }, input) = args.process().await?;
|
||||||
|
let (columns, default_block): (Vec<ColumnPath>, Option<Box<CapturedBlock>>) = 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 context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = vec![];
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(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 context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = columns.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(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<Box<CapturedBlock>>), 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(
|
||||||
|
context: Arc<EvaluationContext>,
|
||||||
|
input: Value,
|
||||||
|
default_block: Arc<Option<Box<CapturedBlock>>>,
|
||||||
|
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();
|
||||||
|
|
||||||
|
context.scope.enter_scope();
|
||||||
|
context.scope.add_vars(&default_block.captured.entries);
|
||||||
|
context.scope.add_var("$it", input.clone());
|
||||||
|
|
||||||
|
let stream = run_block(&default_block.block, &*context, input_stream).await;
|
||||||
|
context.scope.exit_scope();
|
||||||
|
|
||||||
|
let mut stream = stream?;
|
||||||
|
*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,8 @@
|
|||||||
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,
|
||||||
};
|
};
|
||||||
@@ -49,12 +49,8 @@ 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"#
|
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
enter(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
enter(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -78,19 +74,15 @@ documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn enter(
|
async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
raw_args: CommandArgs,
|
let scope = raw_args.scope.clone();
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.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();
|
||||||
let ctrl_c = raw_args.ctrl_c.clone();
|
let ctrl_c = raw_args.ctrl_c.clone();
|
||||||
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, encoding }, _) = raw_args.process(®istry).await?;
|
let (EnterArgs { location, encoding }, _) = raw_args.process().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();
|
||||||
|
|
||||||
@@ -100,7 +92,7 @@ async fn enter(
|
|||||||
if spec.len() == 2 {
|
if spec.len() == 2 {
|
||||||
let (_, command) = (spec[0], spec[1]);
|
let (_, command) = (spec[0], spec[1]);
|
||||||
|
|
||||||
if registry.has(command) {
|
if scope.has_command(command) {
|
||||||
return Ok(OutputStream::one(ReturnSuccess::action(
|
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
CommandAction::EnterHelpShell(
|
CommandAction::EnterHelpShell(
|
||||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||||
@@ -120,25 +112,21 @@ async fn enter(
|
|||||||
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 span = location.span();
|
||||||
|
|
||||||
let (file_extension, contents, contents_tag) = crate::commands::open::fetch(
|
let (file_extension, tagged_contents) = crate::commands::open::fetch(
|
||||||
&full_path,
|
&full_path,
|
||||||
&PathBuf::from(location_clone),
|
&PathBuf::from(location_clone),
|
||||||
tag.span,
|
span,
|
||||||
match encoding {
|
encoding,
|
||||||
Some(e) => e.to_string(),
|
|
||||||
_ => "".to_string(),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await?;
|
.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) = registry.get_command(&command_name) {
|
if let Some(converter) = scope.get_command(&command_name) {
|
||||||
let new_args = RawCommandArgs {
|
let new_args = RawCommandArgs {
|
||||||
host,
|
host,
|
||||||
ctrl_c,
|
ctrl_c,
|
||||||
@@ -150,24 +138,24 @@ async fn enter(
|
|||||||
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 tag = tagged_contents.tag.clone();
|
||||||
let mut result = converter
|
let mut result = converter
|
||||||
.run(new_args.with_input(vec![tagged_contents]), ®istry)
|
.run(new_args.with_input(vec![tagged_contents]))
|
||||||
.await;
|
.await?;
|
||||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
result.drain_vec().await;
|
result.drain_vec().await;
|
||||||
|
|
||||||
Ok(futures::stream::iter(result_vec.into_iter().map(
|
Ok(futures::stream::iter(result_vec.into_iter().map(
|
||||||
move |res| match res {
|
move |res| match res {
|
||||||
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
||||||
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
||||||
value,
|
value,
|
||||||
tag: contents_tag.clone(),
|
tag: tag.clone(),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
x => x,
|
x => x,
|
||||||
@@ -185,13 +173,9 @@ async fn enter(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
let tagged_contents = contents.into_value(contents_tag);
|
|
||||||
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
|
||||||
CommandAction::EnterValueShell(tagged_contents),
|
CommandAction::EnterValueShell(tagged_contents),
|
||||||
)))
|
))),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,11 +183,12 @@ async fn enter(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Enter;
|
use super::Enter;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Enter {})
|
Ok(test_examples(Enter {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,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 {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
@@ -37,12 +36,8 @@ impl WholeStreamCommand for Every {
|
|||||||
"Show (or skip) every n-th row, starting from the first one."
|
"Show (or skip) every n-th row, starting from the first one."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
every(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
every(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -68,38 +63,36 @@ impl WholeStreamCommand for Every {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn every(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let (EveryArgs { stride, skip }, input) = args.process().await?;
|
||||||
let (EveryArgs { stride, skip }, input) = args.process(®istry).await?;
|
|
||||||
let v: Vec<_> = input.into_vec().await;
|
|
||||||
|
|
||||||
let stride_desired = if stride.item < 1 { 1 } else { stride.item } as usize;
|
let stride = stride.item;
|
||||||
|
let skip = skip.item;
|
||||||
|
|
||||||
let mut values_vec_deque = VecDeque::new();
|
Ok(input
|
||||||
|
.enumerate()
|
||||||
for (i, x) in v.iter().enumerate() {
|
.filter_map(move |(i, value)| async move {
|
||||||
let should_include = if skip.item {
|
let stride_desired = if stride < 1 { 1 } else { stride } as usize;
|
||||||
i % stride_desired != 0
|
let should_include = skip == (i % stride_desired != 0);
|
||||||
} else {
|
|
||||||
i % stride_desired == 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_include {
|
if should_include {
|
||||||
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
|
Some(ReturnSuccess::value(value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.to_output_stream())
|
||||||
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Every;
|
use super::Every;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(Every {})
|
Ok(test_examples(Every {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
crates/nu-cli/src/commands/exec.rs
Normal file
81
crates/nu-cli/src/commands/exec.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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) -> Result<OutputStream, ShellError> {
|
||||||
|
exec(args).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) -> Result<OutputStream, ShellError> {
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let name = args.call_info.name_tag.clone();
|
||||||
|
let (args, _): (ExecArgs, _) = args.process().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) -> 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,4 @@
|
|||||||
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};
|
||||||
@@ -20,12 +19,8 @@ impl WholeStreamCommand for Exit {
|
|||||||
"Exit the current shell (or all shells)"
|
"Exit the current shell (or all shells)"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
exit(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
exit(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -44,12 +39,8 @@ impl WholeStreamCommand for Exit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exit(
|
pub async fn exit(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let args = args.evaluate_once().await?;
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let args = args.evaluate_once(®istry).await?;
|
|
||||||
|
|
||||||
let command_action = if args.call_info.args.has("now") {
|
let command_action = if args.call_info.args.has("now") {
|
||||||
CommandAction::Exit
|
CommandAction::Exit
|
||||||
@@ -63,11 +54,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,4 @@
|
|||||||
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};
|
||||||
@@ -30,12 +29,8 @@ impl WholeStreamCommand for First {
|
|||||||
"Show only the first number of rows."
|
"Show only the first number of rows."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
first(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
first(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -57,9 +52,8 @@ impl WholeStreamCommand for First {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let (FirstArgs { rows }, input) = args.process().await?;
|
||||||
let (FirstArgs { rows }, input) = args.process(®istry).await?;
|
|
||||||
let rows_desired = if let Some(quantity) = rows {
|
let rows_desired = if let Some(quantity) = rows {
|
||||||
*quantity
|
*quantity
|
||||||
} else {
|
} else {
|
||||||
@@ -72,11 +66,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
crates/nu-cli/src/commands/flatten.rs
Normal file
183
crates/nu-cli/src/commands/flatten.rs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
rest: Vec<Tagged<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"flatten"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Flatten the table."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
flatten(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "flatten a table",
|
||||||
|
example: "echo [[N, u, s, h, e, l, l]] | flatten | first",
|
||||||
|
result: Some(vec![Value::from("N")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "flatten a column having a nested table",
|
||||||
|
example: "echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal",
|
||||||
|
result: Some(vec![Value::from("arepa")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "restrict the flattening by passing column names",
|
||||||
|
example: "echo [[origin, crate, versions]; [World, $(echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions",
|
||||||
|
result: Some(vec![Value::from("0.22")]),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn flatten(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let (Arguments { rest: columns }, input) = args.process().await?;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |item| {
|
||||||
|
futures::stream::iter(flat_value(&columns, &item, &tag).into_iter().flatten())
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TableInside<'a> {
|
||||||
|
Entries(&'a str, &'a Tag, Vec<&'a Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_value(
|
||||||
|
columns: &[Tagged<String>],
|
||||||
|
item: &Value,
|
||||||
|
name_tag: impl Into<Tag>,
|
||||||
|
) -> Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError> {
|
||||||
|
let tag = item.tag.clone();
|
||||||
|
let name_tag = name_tag.into();
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
if item.is_row() {
|
||||||
|
let mut out = TaggedDictBuilder::new(tag);
|
||||||
|
let mut a_table = None;
|
||||||
|
let mut tables_explicitly_flattened = 0;
|
||||||
|
|
||||||
|
for (column, value) in item.row_entries() {
|
||||||
|
let column_requested = columns.iter().find(|c| c.item == *column);
|
||||||
|
|
||||||
|
if let Value {
|
||||||
|
value: UntaggedValue::Row(Dictionary { entries: mapa }),
|
||||||
|
..
|
||||||
|
} = value
|
||||||
|
{
|
||||||
|
if column_requested.is_none() && !columns.is_empty() {
|
||||||
|
if out.contains_key(&column) {
|
||||||
|
out.insert_value(format!("{}_{}", column, column), value.clone());
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in mapa.into_iter() {
|
||||||
|
if out.contains_key(k) {
|
||||||
|
out.insert_value(format!("{}_{}", column, k), v.clone());
|
||||||
|
} else {
|
||||||
|
out.insert_value(k, v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if value.is_table() {
|
||||||
|
if tables_explicitly_flattened >= 1 && column_requested.is_some() {
|
||||||
|
let attempted = if let Some(name) = column_requested {
|
||||||
|
name.span()
|
||||||
|
} else {
|
||||||
|
name_tag.span
|
||||||
|
};
|
||||||
|
|
||||||
|
let already_flattened =
|
||||||
|
if let Some(TableInside::Entries(_, column_tag, _)) = a_table {
|
||||||
|
column_tag.span
|
||||||
|
} else {
|
||||||
|
name_tag.span
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Error(ShellError::labeled_error_with_secondary(
|
||||||
|
"can only flatten one inner table at the same time",
|
||||||
|
"tried flattening more than one column with inner tables",
|
||||||
|
attempted,
|
||||||
|
"...but is flattened already",
|
||||||
|
already_flattened,
|
||||||
|
))
|
||||||
|
.into_value(name_tag),
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !columns.is_empty() {
|
||||||
|
if let Some(requested) = column_requested {
|
||||||
|
a_table = Some(TableInside::Entries(
|
||||||
|
&requested.item,
|
||||||
|
&requested.tag,
|
||||||
|
value.table_entries().collect(),
|
||||||
|
));
|
||||||
|
|
||||||
|
tables_explicitly_flattened += 1;
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
} else if a_table.is_none() {
|
||||||
|
a_table = Some(TableInside::Entries(
|
||||||
|
&column,
|
||||||
|
&value.tag,
|
||||||
|
value.table_entries().collect(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut expanded = vec![];
|
||||||
|
|
||||||
|
if let Some(TableInside::Entries(column, _, entries)) = a_table {
|
||||||
|
for entry in entries.into_iter() {
|
||||||
|
let mut base = out.clone();
|
||||||
|
base.insert_value(column, entry.clone());
|
||||||
|
expanded.push(base.into_value());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expanded.push(out.into_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded
|
||||||
|
} else if item.is_table() {
|
||||||
|
item.table_entries().map(Clone::clone).collect()
|
||||||
|
} else {
|
||||||
|
vec![item.clone()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res.into_iter().map(ReturnSuccess::value).collect())
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
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;
|
||||||
@@ -32,12 +31,8 @@ impl WholeStreamCommand for Format {
|
|||||||
"Format columns into a string using a simple pattern."
|
"Format columns into a string using a simple pattern."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
format_command(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
format_command(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -49,13 +44,9 @@ impl WholeStreamCommand for Format {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn format_command(
|
async fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
let ctx = Arc::new(EvaluationContext::from_args(&args));
|
||||||
registry: &CommandRegistry,
|
let (FormatArgs { pattern }, input) = args.process().await?;
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = Arc::new(registry.clone());
|
|
||||||
let scope = Arc::new(args.call_info.scope.clone());
|
|
||||||
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
let format_pattern = format(&pattern);
|
let format_pattern = format(&pattern);
|
||||||
let commands = Arc::new(format_pattern);
|
let commands = Arc::new(format_pattern);
|
||||||
@@ -64,8 +55,7 @@ async fn format_command(
|
|||||||
.then(move |value| {
|
.then(move |value| {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let commands = commands.clone();
|
let commands = commands.clone();
|
||||||
let registry = registry.clone();
|
let ctx = ctx.clone();
|
||||||
let scope = scope.clone();
|
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
for command in &*commands {
|
for command in &*commands {
|
||||||
@@ -77,17 +67,13 @@ async fn format_command(
|
|||||||
// FIXME: use the correct spans
|
// FIXME: use the correct spans
|
||||||
let full_column_path = nu_parser::parse_full_column_path(
|
let full_column_path = nu_parser::parse_full_column_path(
|
||||||
&(c.to_string()).spanned(Span::unknown()),
|
&(c.to_string()).spanned(Span::unknown()),
|
||||||
&*registry,
|
&ctx.scope,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = evaluate_baseline_expr(
|
ctx.scope.enter_scope();
|
||||||
&full_column_path.0,
|
ctx.scope.add_var("$it", value.clone());
|
||||||
®istry,
|
let result = evaluate_baseline_expr(&full_column_path.0, &*ctx).await;
|
||||||
&value,
|
ctx.scope.exit_scope();
|
||||||
&scope.vars,
|
|
||||||
&scope.env,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(c) = result {
|
if let Ok(c) = result {
|
||||||
output
|
output
|
||||||
@@ -153,11 +139,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
186
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
186
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use nu_protocol::{
|
||||||
|
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
|
||||||
|
UntaggedValue::Primitive, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use nu_value_ext::get_data_by_column_path;
|
||||||
|
|
||||||
|
use num_format::{Locale, ToFormattedString};
|
||||||
|
|
||||||
|
pub struct FileSize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
field: ColumnPath,
|
||||||
|
format: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for FileSize {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"format filesize"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("format filesize")
|
||||||
|
.required(
|
||||||
|
"field",
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"the name of the column to update",
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"format value",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the format into which convert the filesizes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Converts a column of filesizes to some specified format"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
filesize(args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Convert the size row to KB",
|
||||||
|
example: "ls | format filesize size KB",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert the apparent row to B",
|
||||||
|
example: "du | format filesize apparent B",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_row(
|
||||||
|
input: Value,
|
||||||
|
format: Tagged<String>,
|
||||||
|
field: Arc<ColumnPath>,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
Ok({
|
||||||
|
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||||
|
match replace_for {
|
||||||
|
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
|
||||||
|
Ok(b) => OutputStream::one(ReturnSuccess::value(
|
||||||
|
input.replace_data_at_column_path(&field, b).expect("Given that the existence check was already done, this shouldn't trigger never"),
|
||||||
|
)),
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
},
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filesize(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let (Arguments { field, format }, input) = raw_args.process().await?;
|
||||||
|
let field = Arc::new(field);
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.then(move |input| {
|
||||||
|
let format = format.clone();
|
||||||
|
let field = field.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(input, format, field).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_bytes_to_string_using_format(
|
||||||
|
bytes: Value,
|
||||||
|
format: Tagged<String>,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
match bytes.value {
|
||||||
|
Primitive(Filesize(b)) => {
|
||||||
|
let byte = byte_unit::Byte::from_bytes(b as u128);
|
||||||
|
let value = match format.item().to_lowercase().as_str() {
|
||||||
|
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
|
||||||
|
"kb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
|
||||||
|
)),
|
||||||
|
"kib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
|
||||||
|
)),
|
||||||
|
"mb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
|
||||||
|
)),
|
||||||
|
"mib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
|
||||||
|
)),
|
||||||
|
"gb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
|
||||||
|
)),
|
||||||
|
"gib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
|
||||||
|
)),
|
||||||
|
"tb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
|
||||||
|
)),
|
||||||
|
"tib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
|
||||||
|
)),
|
||||||
|
"pb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
|
||||||
|
)),
|
||||||
|
"pib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
|
||||||
|
)),
|
||||||
|
"eb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
|
||||||
|
)),
|
||||||
|
"eib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
|
||||||
|
)),
|
||||||
|
"zb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
|
||||||
|
)),
|
||||||
|
"zib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
|
||||||
|
)),
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
format!("Invalid format code: {:}", format.item()),
|
||||||
|
"invalid format",
|
||||||
|
format.tag(),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
Ok(b) => Ok(Value { value: b, ..bytes }),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"the data in this row is not of the type filesize",
|
||||||
|
"invalid row type",
|
||||||
|
bytes.tag(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::FileSize;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(FileSize {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod command;
|
||||||
|
pub mod format_filesize;
|
||||||
|
|
||||||
|
pub use command::Format;
|
||||||
|
pub use format_filesize::FileSize;
|
||||||
@@ -19,14 +19,9 @@ impl WholeStreamCommand for From {
|
|||||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
|
||||||
_args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
UntaggedValue::string(crate::commands::help::get_help(&From, &args.scope))
|
||||||
.into_value(Tag::unknown()),
|
.into_value(Tag::unknown()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
@@ -35,11 +30,12 @@ impl WholeStreamCommand for From {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::From;
|
use super::From;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
test_examples(From {})
|
Ok(test_examples(From {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,8 @@ impl WholeStreamCommand for FromCSV {
|
|||||||
"Parse text as .csv and create table."
|
"Parse text as .csv and create table."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
from_csv(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
from_csv(args, registry).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@@ -66,11 +62,7 @@ impl WholeStreamCommand for FromCSV {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_csv(
|
async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
@@ -79,7 +71,7 @@ async fn from_csv(
|
|||||||
separator,
|
separator,
|
||||||
},
|
},
|
||||||
input,
|
input,
|
||||||
) = args.process(®istry).await?;
|
) = args.process().await?;
|
||||||
let sep = match separator {
|
let sep = match separator {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||||
@@ -109,11 +101,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))
|
||||||
}
|
}
|
||||||
@@ -50,6 +54,7 @@ pub async fn from_delimited_data(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name_tag = name;
|
let name_tag = name;
|
||||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||||
|
let sample_lines = concat_string.item.lines().take(3).collect_vec().join("\n");
|
||||||
|
|
||||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||||
Ok(x) => match x {
|
Ok(x) => match x {
|
||||||
@@ -61,10 +66,16 @@ pub async fn from_delimited_data(
|
|||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let line_one = match pretty_csv_error(err) {
|
let line_one = match pretty_csv_error(err) {
|
||||||
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
|
Some(pretty) => format!(
|
||||||
None => format!("Could not parse as {}", format_name),
|
"Could not parse as {} split by '{}' ({})",
|
||||||
|
format_name, sep, pretty
|
||||||
|
),
|
||||||
|
None => format!("Could not parse as {} split by '{}'", format_name, sep),
|
||||||
};
|
};
|
||||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
let line_two = format!(
|
||||||
|
"input cannot be parsed as {} split by '{}'. Input's first lines:\n{}",
|
||||||
|
format_name, sep, sample_lines
|
||||||
|
);
|
||||||
|
|
||||||
Err(ShellError::labeled_error_with_secondary(
|
Err(ShellError::labeled_error_with_secondary(
|
||||||
line_one,
|
line_one,
|
||||||
|
|||||||
@@ -35,12 +35,8 @@ impl WholeStreamCommand for FromEML {
|
|||||||
"Parse text as .eml and create table."
|
"Parse text as .eml and create table."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
&self,
|
from_eml(args).await
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
from_eml(args, registry).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +73,9 @@ fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedVal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_eml(
|
async fn from_eml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let (eml_args, input): (FromEMLArgs, _) = args.process().await?;
|
||||||
let (eml_args, input): (FromEMLArgs, _) = args.process(®istry).await?;
|
|
||||||
let value = input.collect_string(tag.clone()).await?;
|
let value = input.collect_string(tag.clone()).await?;
|
||||||
|
|
||||||
let body_preview = eml_args
|
let body_preview = eml_args
|
||||||
@@ -130,11 +122,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 {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user