mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
675 Commits
Author | SHA1 | Date | |
---|---|---|---|
11a9144e84 | |||
039f223b53 | |||
e1cb026184 | |||
2a96152a43 | |||
0795d56c1c | |||
48a90fea70 | |||
b202951c1d | |||
c3d2c61729 | |||
d7b707939f | |||
991ac6eb77 | |||
011b7c4a07 | |||
617341f8d5 | |||
abd2632977 | |||
5481db4079 | |||
041086d22a | |||
aa564f5072 | |||
8367f2001c | |||
1cfb228924 | |||
b403fb1275 | |||
3443ca40c5 | |||
96f95653a6 | |||
7f7e8465da | |||
e3a273cf73 | |||
233161d56e | |||
d883ab250a | |||
ef4e3f907c | |||
debeadbf3f | |||
d66baaceb9 | |||
85329f9a81 | |||
a5fefaf78b | |||
6f17662a4e | |||
c83aea3c89 | |||
67aaf4cb2d | |||
3083346884 | |||
d07789677f | |||
fb1846120d | |||
da1e1295ea | |||
ecaea57263 | |||
fa928bd25d | |||
c1981dfc26 | |||
fd41fa31d5 | |||
2c52144f41 | |||
87c7898b65 | |||
44e088c6fe | |||
7b4cbd7ce9 | |||
b052d524da | |||
47c4b8e88a | |||
d0a2a02eea | |||
b1e1dab4cb | |||
388973e9ab | |||
2129ec7558 | |||
82f122525c | |||
7c4c00f1e6 | |||
fe6c7dc10a | |||
9bc24e3b12 | |||
833baca66e | |||
9fd92512a2 | |||
b692ca7896 | |||
52dc04a35a | |||
42b1287759 | |||
5a471aa1d0 | |||
a4b8d4a098 | |||
a3be6affa4 | |||
71b99edd48 | |||
64553ddcb7 | |||
2a42482ae9 | |||
11f345a8ae | |||
fec50d8cfe | |||
05e42381df | |||
b435075e09 | |||
430da53f0b | |||
2e6d836dd1 | |||
899d324a9c | |||
576ed6a906 | |||
d744cf8437 | |||
088e662285 | |||
f9b0b81eb2 | |||
c5485c6501 | |||
d8ed01400f | |||
ebc4694e05 | |||
a9441d670e | |||
495d2ebd70 | |||
ad26adc3e3 | |||
63a62e19f9 | |||
4f2ae34df9 | |||
a636f161a4 | |||
dfb1e22559 | |||
dff85a7f70 | |||
3be198d2f5 | |||
d19314fe3a | |||
d06f457b2a | |||
7d07881d96 | |||
3e6e3a207c | |||
481c6d4511 | |||
231a445809 | |||
93e8f6c05e | |||
9de2144fc4 | |||
363dc51ba0 | |||
99117ff2ef | |||
5356cb9fbd | |||
0e13d9fbaa | |||
2dcb16870b | |||
ac9909112f | |||
eb3c2c9e76 | |||
3d29e3efbf | |||
f410fb6689 | |||
eb62fd466e | |||
b50cdd6de8 | |||
f38e2b5c6d | |||
455915ec9e | |||
2333158256 | |||
3ffa804088 | |||
98810d22b1 | |||
5e72b2a797 | |||
7e4e7fa4a6 | |||
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 | |||
0e8a239ae1 | |||
bb08a221e2 | |||
0dbe347f84 | |||
72a21ad619 | |||
6372d2a18c | |||
4468947ad4 | |||
93144a0132 | |||
72f7406057 | |||
c56cbd0f6b | |||
1420cbafe4 | |||
053bd926ec | |||
d095cb91e4 | |||
e8476d8fbb | |||
7532618bdc | |||
e3e1e6f81b | |||
bce6f5a3e6 | |||
480600c465 | |||
89c737f456 | |||
4e83363dd3 | |||
de6d8738c4 | |||
853d7e7120 | |||
b0c30098e4 | |||
fcbaefed52 | |||
77e02ac1c1 | |||
088901b24f | |||
ed7a62bca3 | |||
6bfd8532e4 | |||
bc9cc75c8a | |||
53a6e9f0bd | |||
5f9de80d9b | |||
353b33be1b | |||
96d58094cf | |||
94aac0e8dd | |||
9f54d238ba | |||
778e497903 | |||
6914099e28 | |||
1b6f94b46c | |||
3d63901b3b | |||
eb1ada6115 | |||
831111edec | |||
29ea29261d | |||
ee835f75db | |||
bd7ac0d48e | |||
d7b1480ad0 | |||
86b316e930 | |||
a042f407c1 | |||
40673e4599 | |||
bcfb084d4c | |||
a1fd5ad128 | |||
fe6d96e996 | |||
e24e0242d1 | |||
c959dc1ee3 | |||
d82ce26b44 | |||
935a5f6b9e | |||
731aa6bbdd | |||
a268e825aa | |||
982f067d0e |
@ -1,11 +1,20 @@
|
||||
trigger:
|
||||
- master
|
||||
- main
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux-stable:
|
||||
image: ubuntu-18.04
|
||||
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:
|
||||
image: macos-10.14
|
||||
style: 'unflagged'
|
||||
@ -34,6 +43,7 @@ steps:
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
sudo npm install -g wasm-pack
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
@ -41,23 +51,32 @@ steps:
|
||||
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
fi
|
||||
rustup update
|
||||
rustc -Vv
|
||||
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
rustup component add rustfmt
|
||||
# rustup update
|
||||
# rustc -Vv
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: cd samples/wasm && wasm-pack build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
||||
|
@ -1,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:
|
||||
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:
|
||||
jobs:
|
||||
- docker/publish:
|
||||
@ -39,7 +39,7 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- main
|
||||
before_build:
|
||||
- pull_cache
|
||||
after_build:
|
||||
@ -73,7 +73,7 @@ workflows:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v.*/
|
||||
only: /^\d+\.\d+\.\d+$/
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:latest
|
||||
- run: docker pull quay.io/nushell/nu-base:latest
|
||||
@ -98,11 +98,11 @@ workflows:
|
||||
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:
|
||||
jobs:
|
||||
|
||||
# Deploy devel tag on merge to master
|
||||
# Deploy devel tag on merge to main
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
@ -113,7 +113,7 @@ workflows:
|
||||
- pull_cache
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
only: main
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
@ -137,7 +137,7 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
- docker/publish:
|
||||
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.
|
||||
|
||||
**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.**
|
||||
|
286
.github/workflows/release.yml
vendored
Normal file
286
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
name: Create Release Draft
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['[0-9]+.[0-9]+.[0-9]+*']
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install libxcb
|
||||
run: sudo apt-get install libxcb-composite0-dev
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: output/*
|
||||
|
||||
macos:
|
||||
name: Build macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: output/*
|
||||
|
||||
windows:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Add cargo-wix subcommand
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-wix
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Download Less Binary
|
||||
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
|
||||
|
||||
- name: Download Less License
|
||||
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target\release\nu.exe output\
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
# Note: If the version of `less.exe` needs to be changed, update this URL
|
||||
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
|
||||
# moved this stuff down to create wix after we download less
|
||||
|
||||
- name: Create msi with wix
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: wix
|
||||
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: target\wix\nushell-windows.msi
|
||||
|
||||
- name: Upload zip
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: output\*
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- linux
|
||||
- macos
|
||||
- windows
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Determine Release Info
|
||||
id: info
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
VERSION=${GITHUB_REF##*/}
|
||||
MAJOR=${VERSION%%.*}
|
||||
MINOR=${VERSION%.*}
|
||||
MINOR=${MINOR#*.}
|
||||
PATCH=${VERSION##*.}
|
||||
echo "::set-output name=version::${VERSION}"
|
||||
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
|
||||
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
||||
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
||||
echo "::set-output name=innerdir::nushell-${VERSION}"
|
||||
|
||||
- name: Create Release Draft
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ steps.info.outputs.version }} Release
|
||||
draft: true
|
||||
|
||||
- name: Create Linux Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download Linux Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Restore Linux File Modes
|
||||
run: |
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
||||
|
||||
- name: Create Linux tarball
|
||||
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
||||
|
||||
- name: Upload Linux Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: Create macOS Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Restore macOS File Modes
|
||||
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
||||
|
||||
- name: Upload macOS Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create Windows Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download Windows zip
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Show Windows Artifacts
|
||||
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
||||
|
||||
- name: Upload Windows zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Download Windows installer
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: ./
|
||||
|
||||
- name: Upload Windows installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./nushell-windows.msi
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
|
||||
asset_content_type: applictaion/x-msi
|
6
.gitpod.Dockerfile
vendored
6
.gitpod.Dockerfile
vendored
@ -1,5 +1,9 @@
|
||||
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
|
||||
|
||||
RUN sudo apt-get update && \
|
||||
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
|
||||
rust-lldb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUST_LLDB=/usr/bin/lldb-8
|
||||
ENV RUST_LLDB=/usr/bin/lldb-11
|
||||
|
@ -4,7 +4,7 @@ tasks:
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable,test-bins
|
||||
init: cargo test --all --features=stable
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
|
@ -68,9 +68,9 @@ members of the project's leadership.
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
<https://www.contributor-covenant.org/faq>
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
||||
@ -9,50 +11,65 @@ For speedy contributions open it in Gitpod, nu will be pre-installed with the la
|
||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||
<!--WIP-->
|
||||
|
||||
# Developing
|
||||
## Set up
|
||||
## Developing
|
||||
|
||||
### Set up
|
||||
|
||||
This is no different than other Rust projects.
|
||||
|
||||
```shell
|
||||
```bash
|
||||
git clone https://github.com/nushell/nushell
|
||||
cd nushell
|
||||
cargo build
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
### Useful Commands
|
||||
|
||||
Build and run Nushell:
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
Run Clippy on Nushell:
|
||||
- Build and run with extra features:
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
Run all tests:
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
- Run all tests:
|
||||
|
||||
Run all tests for a specific command
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
- Run all tests for a specific command
|
||||
|
||||
Check to see if there are code formatting issues
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
- Check to see if there are code formatting issues
|
||||
|
||||
Format the code in the project
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
- Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
|
||||
```
|
||||
|
4940
Cargo.lock
generated
4940
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
257
Cargo.toml
257
Cargo.toml
@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "nu"
|
||||
version = "0.15.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A new type of shell"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
default-run = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
homepage = "https://www.nushell.sh"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
edition = "2018"
|
||||
exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.27.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,66 +18,139 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.15.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.15.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.15.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.15.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.15.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.15.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.15.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_binaryview = { version = "0.15.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.15.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.15.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.15.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.15.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.15.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_start = { version = "0.15.0", path = "./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_sys = { version = "0.15.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.15.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.15.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
nu-cli = { version = "0.27.0", path = "./crates/nu-cli", default-features = false }
|
||||
nu-command = { version = "0.27.0", path = "./crates/nu-command" }
|
||||
nu-data = { version = "0.27.0", path = "./crates/nu-data" }
|
||||
nu-engine = { version = "0.27.0", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.27.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.27.0", path = "./crates/nu-parser" }
|
||||
nu-plugin = { version = "0.27.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.27.0", path = "./crates/nu-protocol" }
|
||||
nu-source = { version = "0.27.0", path = "./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.27.0", path = "./crates/nu-value-ext" }
|
||||
|
||||
crossterm = { version = "0.17.5", optional = true }
|
||||
semver = { version = "0.10.0", optional = true }
|
||||
syntect = { version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
|
||||
url = { version = "2.1.1", optional = true }
|
||||
nu_plugin_binaryview = { version = "0.27.0", path = "./crates/nu_plugin_binaryview", optional = true }
|
||||
nu_plugin_chart = { version = "0.27.0", path = "./crates/nu_plugin_chart", optional = true }
|
||||
nu_plugin_fetch = { version = "0.27.0", path = "./crates/nu_plugin_fetch", optional = true }
|
||||
nu_plugin_from_bson = { version = "0.27.0", path = "./crates/nu_plugin_from_bson", optional = true }
|
||||
nu_plugin_from_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
|
||||
nu_plugin_inc = { version = "0.27.0", path = "./crates/nu_plugin_inc", optional = true }
|
||||
nu_plugin_match = { version = "0.27.0", path = "./crates/nu_plugin_match", optional = true }
|
||||
nu_plugin_post = { version = "0.27.0", path = "./crates/nu_plugin_post", optional = true }
|
||||
nu_plugin_ps = { version = "0.27.0", path = "./crates/nu_plugin_ps", optional = true }
|
||||
nu_plugin_s3 = { version = "0.27.0", path = "./crates/nu_plugin_s3", optional = true }
|
||||
nu_plugin_selector = { version = "0.27.0", path = "./crates/nu_plugin_selector", optional = true }
|
||||
nu_plugin_start = { version = "0.27.0", path = "./crates/nu_plugin_start", optional = true }
|
||||
nu_plugin_sys = { version = "0.27.0", path = "./crates/nu_plugin_sys", optional = true }
|
||||
nu_plugin_textview = { version = "0.27.0", path = "./crates/nu_plugin_textview", optional = true }
|
||||
nu_plugin_to_bson = { version = "0.27.0", path = "./crates/nu_plugin_to_bson", optional = true }
|
||||
nu_plugin_to_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
|
||||
nu_plugin_tree = { version = "0.27.0", path = "./crates/nu_plugin_tree", optional = true }
|
||||
nu_plugin_xpath = { version = "0.27.0", path = "./crates/nu_plugin_xpath", optional = true }
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.0"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
log = "0.4.8"
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.15.0", path = "./crates/nu-test-support" }
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = { version = "0.27.0", path = "./crates/nu-test-support" }
|
||||
|
||||
[build-dependencies]
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
nu-build = { version = "0.15.0", path = "./crates/nu-build" }
|
||||
|
||||
[features]
|
||||
default = ["sys", "ps", "textview", "inc"]
|
||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
directories-support = [
|
||||
"nu-cli/directories",
|
||||
"nu-cli/dirs",
|
||||
"nu-command/directories",
|
||||
"nu-command/dirs",
|
||||
"nu-data/directories",
|
||||
"nu-data/dirs",
|
||||
"nu-engine/dirs",
|
||||
]
|
||||
ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-cli/term", "nu-command/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
|
||||
which-support = [
|
||||
"nu-cli/ichwh",
|
||||
"nu-cli/which",
|
||||
"nu-command/ichwh",
|
||||
"nu-command/which",
|
||||
]
|
||||
|
||||
# Default
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
default = [
|
||||
"nu-cli/shadow-rs",
|
||||
"sys",
|
||||
"ps",
|
||||
"textview",
|
||||
"inc",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"zip-support",
|
||||
]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"tree",
|
||||
"clipboard-cli",
|
||||
"trash-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
"s3",
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
]
|
||||
|
||||
wasi = ["inc", "match", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
|
||||
trace = ["nu-parser/trace"]
|
||||
|
||||
# Stable (Default)
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
inc = ["nu_plugin_inc"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
trace = ["nu-parser/trace"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
start = ["nu_plugin_start"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli", "nu-command/clipboard-cli"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
|
||||
[profile.release]
|
||||
#strip = "symbols" #Couldn't get working +nightly
|
||||
codegen-units = 1 #Reduce parallel codegen units
|
||||
lto = true #Link Time Optimization
|
||||
opt-level = 'z' #Optimize for size
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -102,37 +175,83 @@ name = "nu_plugin_core_sys"
|
||||
path = "src/plugins/nu_plugin_core_sys.rs"
|
||||
required-features = ["sys"]
|
||||
|
||||
# Stable plugins
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_fetch"
|
||||
path = "src/plugins/nu_plugin_stable_fetch.rs"
|
||||
name = "nu_plugin_core_fetch"
|
||||
path = "src/plugins/nu_plugin_core_fetch.rs"
|
||||
required-features = ["fetch"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_binaryview"
|
||||
path = "src/plugins/nu_plugin_stable_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_match"
|
||||
path = "src/plugins/nu_plugin_stable_match.rs"
|
||||
name = "nu_plugin_core_match"
|
||||
path = "src/plugins/nu_plugin_core_match.rs"
|
||||
required-features = ["match"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_post"
|
||||
path = "src/plugins/nu_plugin_stable_post.rs"
|
||||
name = "nu_plugin_core_post"
|
||||
path = "src/plugins/nu_plugin_core_post.rs"
|
||||
required-features = ["post"]
|
||||
|
||||
# Extra plugins
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_tree"
|
||||
path = "src/plugins/nu_plugin_stable_tree.rs"
|
||||
name = "nu_plugin_extra_binaryview"
|
||||
path = "src/plugins/nu_plugin_extra_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_start"
|
||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[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
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
1
README.build.txt
Normal file
1
README.build.txt
Normal file
@ -0,0 +1 @@
|
||||
Nu will look for the plugins in your PATH on startup. While nu will have some functionality without them, for full functionality you'll need to copy them into your path so they can be loaded.
|
207
README.md
207
README.md
@ -1,17 +1,19 @@
|
||||
# README
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||
|
||||
|
||||
# Nu Shell
|
||||
## Nushell
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
# Status
|
||||
## Status
|
||||
|
||||
This project has reached a minimum-viable product level of quality.
|
||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
||||
@ -21,7 +23,7 @@ Its design is also subject to change as it matures.
|
||||
Nu comes with a set of built-in commands (listed below).
|
||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
||||
|
||||
# Learning more
|
||||
## Learning more
|
||||
|
||||
There are a few good resources to learn about Nu.
|
||||
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
||||
@ -32,45 +34,45 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
|
||||
# Installation
|
||||
## Installation
|
||||
|
||||
## Local
|
||||
### Local
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/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:
|
||||
|
||||
* pkg-config and libssl (only needed on Linux)
|
||||
* on Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
- pkg-config and libssl (only needed on Linux)
|
||||
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
* To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
* on Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
- 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`
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
```
|
||||
```bash
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
cargo build --workspace --features=stable
|
||||
```bash
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
## Docker
|
||||
### Docker
|
||||
|
||||
### Quickstart
|
||||
#### Quickstart
|
||||
|
||||
Want to try Nu right away? Execute the following to get started.
|
||||
|
||||
@ -78,14 +80,14 @@ Want to try Nu right away? Execute the following to get started.
|
||||
docker run -it quay.io/nushell/nu:latest
|
||||
```
|
||||
|
||||
### Guide
|
||||
#### Guide
|
||||
|
||||
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
||||
on Quay.io. Pulling a container would come down to:
|
||||
|
||||
```bash
|
||||
$ docker pull quay.io/nushell/nu
|
||||
$ docker pull quay.io/nushell/nu-base
|
||||
docker pull quay.io/nushell/nu
|
||||
docker pull quay.io/nushell/nu-base
|
||||
```
|
||||
|
||||
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
||||
@ -95,41 +97,41 @@ Optionally, you can also build the containers locally using the [dockerfiles pro
|
||||
To build the base image:
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
```
|
||||
|
||||
And then to build the smaller container (using a Multistage build):
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile -t nushell/nu .
|
||||
docker build -f docker/Dockerfile -t nushell/nu .
|
||||
```
|
||||
|
||||
Either way, you can run either container as follows:
|
||||
|
||||
```bash
|
||||
$ docker run -it nushell/nu-base
|
||||
$ docker run -it nushell/nu
|
||||
docker run -it nushell/nu-base
|
||||
docker run -it nushell/nu
|
||||
/> exit
|
||||
```
|
||||
|
||||
The second container is a bit smaller if the size is important to you.
|
||||
|
||||
## Packaging status
|
||||
### Packaging status
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
|
||||
### Fedora
|
||||
#### Fedora
|
||||
|
||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
||||
|
||||
# Philosophy
|
||||
## Philosophy
|
||||
|
||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
|
||||
## Pipelines
|
||||
### Pipelines
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||
@ -137,41 +139,41 @@ Just as the Unix philosophy, Nu allows commands to output from stdout and read f
|
||||
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
* Commands that produce a stream (eg, `ls`)
|
||||
* Commands that filter a stream (eg, `where type == "Directory"`)
|
||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
- Commands that produce a stream (eg, `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
- Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬────────┬──────────────
|
||||
```shell
|
||||
> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬───────┬──────────────
|
||||
# │ name │ type │ size │ modified
|
||||
───┼────────┼──────┼────────┼──────────────
|
||||
0 │ assets │ Dir │ 4.1 KB │ 1 week ago
|
||||
1 │ crates │ Dir │ 4.1 KB │ 4 days ago
|
||||
2 │ debian │ Dir │ 4.1 KB │ 1 week ago
|
||||
3 │ docker │ Dir │ 4.1 KB │ 1 week ago
|
||||
4 │ docs │ Dir │ 4.1 KB │ 1 week ago
|
||||
5 │ images │ Dir │ 4.1 KB │ 1 week ago
|
||||
6 │ src │ Dir │ 4.1 KB │ 1 week ago
|
||||
7 │ target │ Dir │ 4.1 KB │ 23 hours ago
|
||||
8 │ tests │ Dir │ 4.1 KB │ 1 week ago
|
||||
───┴────────┴──────┴────────┴──────────────
|
||||
───┼────────┼──────┼───────┼──────────────
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
||||
3 │ docker │ Dir │ 288 B │ 3 months ago
|
||||
4 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||
5 │ images │ Dir │ 160 B │ 5 months ago
|
||||
6 │ src │ Dir │ 128 B │ 1 day ago
|
||||
7 │ target │ Dir │ 160 B │ 5 days ago
|
||||
8 │ tests │ Dir │ 192 B │ 3 months ago
|
||||
───┴────────┴──────┴───────┴──────────────
|
||||
```
|
||||
|
||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
||||
We could have also written the above:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> ls | where type == Directory
|
||||
```shell
|
||||
> ls | where type == Dir
|
||||
```
|
||||
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
||||
|
||||
```text
|
||||
/home/jonathan/Source/nushell(master)> ps | where cpu > 0
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
||||
@ -183,16 +185,16 @@ For example, we could use the built-in `ps` command as well to get a list of the
|
||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
||||
```
|
||||
|
||||
## Opening files
|
||||
### Opening files
|
||||
|
||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
||||
For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml
|
||||
```shell
|
||||
> open Cargo.toml
|
||||
────────────────────┬───────────────────────────
|
||||
bin │ [table 18 rows]
|
||||
build-dependencies │ [row nu-build serde toml]
|
||||
build-dependencies │ [row serde toml]
|
||||
dependencies │ [row 29 columns]
|
||||
dev-dependencies │ [row nu-test-support]
|
||||
features │ [row 19 columns]
|
||||
@ -203,8 +205,8 @@ For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
We can pipeline this into a command that gets the contents of one of the columns:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package
|
||||
```shell
|
||||
> open Cargo.toml | get package
|
||||
───────────────┬────────────────────────────────────
|
||||
authors │ [table 1 rows]
|
||||
default-run │ nu
|
||||
@ -217,31 +219,31 @@ We can pipeline this into a command that gets the contents of one of the columns
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.14.1
|
||||
version │ 0.21.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
|
||||
0.14.1
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.21.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
|
||||
## 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:
|
||||
|
||||
```
|
||||
> config --set [edit_mode "vi"]
|
||||
> config --set [path $nu.path]
|
||||
```shell
|
||||
> config set edit_mode "vi"
|
||||
> config set path $nu.path
|
||||
```
|
||||
|
||||
## Shells
|
||||
### Shells
|
||||
|
||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
||||
@ -252,7 +254,7 @@ Once you're done with a shell, you can `exit` it and remove it from the ring buf
|
||||
|
||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
||||
|
||||
## Plugins
|
||||
### Plugins
|
||||
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
||||
This allows you to extend nu for your needs.
|
||||
@ -264,50 +266,59 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
# Goals
|
||||
## Goals
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
* First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
|
||||
* Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
|
||||
* Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
||||
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
||||
|
||||
* Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
||||
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
||||
|
||||
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
|
||||
# Commands
|
||||
## Commands
|
||||
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
|
||||
|
||||
# Progress
|
||||
## Progress
|
||||
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
|
||||
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
|
||||
| Functions| X | | | | | No functions, yet, only aliases
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Completions | | X | | | | Completions are currently barebones, at best
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features |
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables |
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable |
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish |
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons |
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | X | | | | No functions, yet, only aliases |
|
||||
| Variables | | X | | | | Nu doesn't yet support variables |
|
||||
| Completions | | X | | | | Completions are currently barebones, at best |
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked |
|
||||
|
||||
# Contributing
|
||||
## Current Roadmap
|
||||
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details.
|
||||
|
||||
# License
|
||||
Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
The project is made available under the MIT license. See the `LICENSE` file for more information.
|
||||
|
||||
|
60
TODO.md
60
TODO.md
@ -1,60 +0,0 @@
|
||||
This pattern is extremely repetitive and can be abstracted:
|
||||
|
||||
```rs
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
|
||||
let mut concat_string = String::new();
|
||||
let mut latest_tag: Option<Tag> = None;
|
||||
|
||||
for value in values {
|
||||
latest_tag = Some(value_tag.clone());
|
||||
let value_span = value.tag.span;
|
||||
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
concat_string.push_str(&s);
|
||||
concat_string.push_str("\n");
|
||||
}
|
||||
_ => yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
)),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Mandatory and Optional in parse_command
|
||||
|
||||
trace_remaining?
|
||||
|
||||
select_fields and select_fields take unnecessary Tag
|
||||
|
||||
Value#value should be Value#untagged
|
||||
|
||||
Unify dictionary building, probably around a macro
|
||||
|
||||
sys plugin in own crate
|
||||
|
||||
textview in own crate
|
||||
|
||||
Combine atomic and atomic_parse in parser
|
||||
|
||||
at_end_possible_ws needs to be comment and separator sensitive
|
||||
|
||||
Eliminate unnecessary `nodes` parser
|
||||
|
||||
#[derive(HasSpan)]
|
||||
|
||||
Figure out a solution for the duplication in stuff like NumberShape vs. NumberExpressionShape
|
||||
|
||||
use `struct Expander` from signature.rs
|
Binary file not shown.
Binary file not shown.
3
build.rs
3
build.rs
@ -1,3 +0,0 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
nu_build::build()
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "nu-build"
|
||||
version = "0.15.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
edition = "2018"
|
||||
description = "Core build system for nushell"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.53"
|
||||
toml = "0.5.6"
|
@ -1,80 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref WORKSPACES: Mutex<BTreeMap<String, &'static Path>> = Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
|
||||
|
||||
fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
|
||||
let mut workspaces = WORKSPACES.lock()?;
|
||||
if let Some(rv) = workspaces.get(manifest_dir) {
|
||||
Ok(Some(rv))
|
||||
} else {
|
||||
#[derive(Deserialize)]
|
||||
struct Manifest {
|
||||
workspace_root: String,
|
||||
}
|
||||
let output = std::process::Command::new(env!("CARGO"))
|
||||
.arg("metadata")
|
||||
.arg("--format-version=1")
|
||||
.current_dir(manifest_dir)
|
||||
.output()?;
|
||||
let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
|
||||
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
|
||||
workspaces.insert(manifest_dir.to_string(), path.as_path());
|
||||
Ok(workspaces.get(manifest_dir).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Feature {
|
||||
#[allow(unused)]
|
||||
description: String,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = env::var("CARGO_MANIFEST_DIR")?;
|
||||
|
||||
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
|
||||
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
|
||||
.map(|s| s.split(',').map(|s| s.to_string()).collect())
|
||||
.unwrap_or_else(|_| HashSet::new());
|
||||
|
||||
if all_on && !flags.is_empty() {
|
||||
println!(
|
||||
"cargo:warning=Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
|
||||
);
|
||||
}
|
||||
|
||||
let workspace = match get_cargo_workspace(&input)? {
|
||||
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
|
||||
None => return Ok(()),
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
let path = Path::new(&workspace).join("features.toml");
|
||||
|
||||
// If the crate is being downloaded from crates.io, it won't have a features.toml, and that's ok
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
|
||||
|
||||
for (key, value) in toml.iter() {
|
||||
if value.enabled || all_on || flags.contains(key) {
|
||||
println!("cargo:rustc-cfg={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,114 +1,135 @@
|
||||
[package]
|
||||
name = "nu-cli"
|
||||
version = "0.15.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.27.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-source = { version = "0.15.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.15.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.15.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.15.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.15.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.15.0", path = "../nu-value-ext" }
|
||||
nu-test-support = { version = "0.15.0", path = "../nu-test-support" }
|
||||
nu-command = { version = "0.27.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.27.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.27.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.27.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.27.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.27.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.27.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.27.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.27.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.27.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.27.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.27.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.27.0", path = "../nu-value-ext" }
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.31"
|
||||
directories = "2.0.2"
|
||||
async-stream = "0.2"
|
||||
base64 = "0.12.1"
|
||||
bigdecimal = { version = "0.1.2", features = ["serde"] }
|
||||
bson = { version = "0.14.1", features = ["decimal128"] }
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.4"
|
||||
calamine = "0.16"
|
||||
cfg-if = "0.1"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.1"
|
||||
csv = "1.1"
|
||||
ctrlc = "3.1.4"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
async-recursion = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "1.0.1"
|
||||
calamine = "0.17.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.11.0"
|
||||
csv = "1.1.5"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
derive-new = "0.5.8"
|
||||
dirs = "2.0.2"
|
||||
dunce = "1.0.0"
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.28"
|
||||
filesize = "0.2.0"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.5"
|
||||
futures_codec = "0.4"
|
||||
fs_extra = "1.2.0"
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.12"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = { version = "0.13.6", default_features = false }
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = "0.3.4"
|
||||
indexmap = { version = "1.3.2", features = ["serde-1"] }
|
||||
itertools = "0.9.0"
|
||||
codespan-reporting = "0.9.4"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
||||
num-traits = "0.2.11"
|
||||
parking_lot = "0.10.2"
|
||||
ical = "0.7.0"
|
||||
ichwh = { version = "0.3.4", optional = true }
|
||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.14"
|
||||
meval = "0.2.0"
|
||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
parking_lot = "0.11.1"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
prettytable-rs = "0.8.0"
|
||||
ptree = {version = "0.2" }
|
||||
pretty-hex = "0.2.1"
|
||||
ptree = { version = "0.3.1", optional = true }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.11.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.4"
|
||||
quick-xml = "0.21.0"
|
||||
rand = "0.8.3"
|
||||
rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "6.3.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.53"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
shellexpand = "2.0.0"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
shellexpand = "2.1.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = "0.5.2"
|
||||
termcolor = "1.1.0"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.4"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.0"
|
||||
which = "3"
|
||||
|
||||
trash = { version = "1.0.1", optional = true }
|
||||
clipboard = { version = "0.5", optional = true }
|
||||
starship = { version = "0.41.3", optional = true }
|
||||
rayon = "1.3.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.2.0"
|
||||
term = { version = "0.7.0", optional = true }
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.2"
|
||||
titlecase = "1.1.0"
|
||||
toml = "0.5.8"
|
||||
trash = { version = "1.3.0", optional = true }
|
||||
unicode-segmentation = "1.7.1"
|
||||
url = "2.1.1"
|
||||
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
|
||||
which = { version = "4.0.2", optional = true }
|
||||
zip = { version = "0.5.9", optional = true }
|
||||
shadow-rs = { version = "0.5", default-features = false, optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
umask = "1.0.0"
|
||||
users = "0.11.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]
|
||||
version = "0.23.1"
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.2"
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = { version = "0.15.0", path = "../nu-build" }
|
||||
shadow-rs = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
|
||||
[features]
|
||||
default = ["shadow-rs"]
|
||||
clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
clipboard-cli = ["clipboard"]
|
||||
trash-support = ["trash"]
|
||||
dirs = ["dirs-next"]
|
||||
directories = ["directories-next"]
|
||||
|
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
@ -1,262 +0,0 @@
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod average;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod calc;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod compact;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod count;
|
||||
pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod enter;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod evaluate_by;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod from;
|
||||
pub(crate) mod from_bson;
|
||||
pub(crate) mod from_csv;
|
||||
pub(crate) mod from_eml;
|
||||
pub(crate) mod from_ics;
|
||||
pub(crate) mod from_ini;
|
||||
pub(crate) mod from_json;
|
||||
pub(crate) mod from_ods;
|
||||
pub(crate) mod from_sqlite;
|
||||
pub(crate) mod from_ssv;
|
||||
pub(crate) mod from_toml;
|
||||
pub(crate) mod from_tsv;
|
||||
pub(crate) mod from_url;
|
||||
pub(crate) mod from_vcf;
|
||||
pub(crate) mod from_xlsx;
|
||||
pub(crate) mod from_xml;
|
||||
pub(crate) mod from_yaml;
|
||||
pub(crate) mod get;
|
||||
pub(crate) mod group_by;
|
||||
pub(crate) mod group_by_date;
|
||||
pub(crate) mod headers;
|
||||
pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod keep_until;
|
||||
pub(crate) mod keep_while;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod map_max_by;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod mv;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod pivot;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
pub(crate) mod range;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod reduce_by;
|
||||
pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
pub(crate) mod rm;
|
||||
pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod skip_until;
|
||||
pub(crate) mod skip_while;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
pub(crate) mod str_;
|
||||
pub(crate) mod sum;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod t_sort_by;
|
||||
pub(crate) mod table;
|
||||
pub(crate) mod tags;
|
||||
pub(crate) mod to;
|
||||
pub(crate) mod to_bson;
|
||||
pub(crate) mod to_csv;
|
||||
pub(crate) mod to_html;
|
||||
pub(crate) mod to_json;
|
||||
pub(crate) mod to_md;
|
||||
pub(crate) mod to_sqlite;
|
||||
pub(crate) mod to_toml;
|
||||
pub(crate) mod to_tsv;
|
||||
pub(crate) mod to_url;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod trim;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
pub(crate) mod wrap;
|
||||
|
||||
pub(crate) use autoview::Autoview;
|
||||
pub(crate) use cd::Cd;
|
||||
pub(crate) use command::{
|
||||
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use average::Average;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use calc::Calc;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::Config;
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::Date;
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use enter::Enter;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use evaluate_by::EvaluateBy;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_bson::FromBSON;
|
||||
pub(crate) use from_csv::FromCSV;
|
||||
pub(crate) use from_eml::FromEML;
|
||||
pub(crate) use from_ics::FromIcs;
|
||||
pub(crate) use from_ini::FromINI;
|
||||
pub(crate) use from_json::FromJSON;
|
||||
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_toml::FromTOML;
|
||||
pub(crate) use from_tsv::FromTSV;
|
||||
pub(crate) use from_url::FromURL;
|
||||
pub(crate) use from_vcf::FromVcf;
|
||||
pub(crate) use from_xlsx::FromXLSX;
|
||||
pub(crate) use from_xml::FromXML;
|
||||
pub(crate) use from_yaml::FromYAML;
|
||||
pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use get::Get;
|
||||
pub(crate) use group_by::GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use keep::Keep;
|
||||
pub(crate) use keep_until::KeepUntil;
|
||||
pub(crate) use keep_while::KeepWhile;
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use map_max_by::MapMaxBy;
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use mv::Move;
|
||||
pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
pub(crate) use range::Range;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use reduce_by::ReduceBy;
|
||||
pub(crate) use reject::Reject;
|
||||
pub(crate) use rename::Rename;
|
||||
pub(crate) use reverse::Reverse;
|
||||
pub(crate) use rm::Remove;
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Select;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::Skip;
|
||||
pub(crate) use skip_until::SkipUntil;
|
||||
pub(crate) use skip_while::SkipWhile;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use split::Split;
|
||||
pub(crate) use split::SplitColumn;
|
||||
pub(crate) use split::SplitRow;
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
||||
};
|
||||
pub(crate) use sum::Sum;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use t_sort_by::TSortBy;
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
pub(crate) use to::To;
|
||||
pub(crate) use to_bson::ToBSON;
|
||||
pub(crate) use to_csv::ToCSV;
|
||||
pub(crate) use to_html::ToHTML;
|
||||
pub(crate) use to_json::ToJSON;
|
||||
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_tsv::ToTSV;
|
||||
pub(crate) use to_url::ToURL;
|
||||
pub(crate) use to_yaml::ToYAML;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use trim::Trim;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use version::Version;
|
||||
pub(crate) use what::What;
|
||||
pub(crate) use where_::Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
@ -1,137 +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> {
|
||||
// args.process(registry, alias)?.run()
|
||||
alias(args, registry)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// <<<<<<< HEAD
|
||||
// pub fn alias(alias_args: AliasArgs, ctx: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
// =======
|
||||
pub fn alias(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
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(raw_input.len());
|
||||
let mut left = raw_input[..left_brace].replace("--save", "").replace("-s", "");
|
||||
let mut 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 mut 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 {
|
||||
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
|
||||
}
|
||||
}
|
||||
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), processed_args, block.clone()))
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AppendArgs {
|
||||
row: Value,
|
||||
}
|
||||
|
||||
pub struct Append;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Append {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("append").required(
|
||||
"row value",
|
||||
SyntaxShape::Any,
|
||||
"the value of the row to append to the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append the given row to the table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
||||
|
||||
let eos = futures::stream::iter(vec![row]);
|
||||
|
||||
Ok(input.chain(eos).to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Add something to the end of a list or table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Append;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Append {})
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use bigdecimal::FromPrimitive;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{convert_number_to_u64, Number, Operator};
|
||||
use nu_protocol::{
|
||||
Dictionary, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value,
|
||||
};
|
||||
use num_traits::identities::Zero;
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
pub struct Average;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Average {
|
||||
fn name(&self) -> &str {
|
||||
"average"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("average")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Average the values."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
average(RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
})
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Average a list of numbers",
|
||||
example: "echo [100 0 100 0] | average",
|
||||
result: Some(vec![UntaggedValue::decimal(50).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn average(
|
||||
RunnableContext {
|
||||
mut input, name, ..
|
||||
}: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut values: Vec<Value> = input.drain_vec().await;
|
||||
let action = reducer_for(Reduce::Sum);
|
||||
|
||||
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
|
||||
match avg(&values, name) {
|
||||
Ok(result) => yield ReturnSuccess::value(result),
|
||||
Err(err) => yield Err(err),
|
||||
}
|
||||
} else {
|
||||
let mut column_values = IndexMap::new();
|
||||
for value in values {
|
||||
match value.value {
|
||||
UntaggedValue::Row(row_dict) => {
|
||||
for (key, value) in row_dict.entries.iter() {
|
||||
column_values
|
||||
.entry(key.clone())
|
||||
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
|
||||
.or_insert(vec![value.clone()]);
|
||||
}
|
||||
},
|
||||
table => {},
|
||||
};
|
||||
}
|
||||
|
||||
let mut column_totals = IndexMap::new();
|
||||
for (col_name, col_vals) in column_values {
|
||||
match avg(&col_vals, &name) {
|
||||
Ok(result) => {
|
||||
column_totals.insert(col_name, result);
|
||||
}
|
||||
Err(err) => yield Err(err),
|
||||
}
|
||||
}
|
||||
yield ReturnSuccess::value(
|
||||
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
|
||||
}
|
||||
};
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn avg(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let name = name.into();
|
||||
|
||||
let sum = reducer_for(Reduce::Sum);
|
||||
|
||||
let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal");
|
||||
|
||||
let total_rows = UntaggedValue::decimal(number);
|
||||
let total = sum(Value::zero(), values.to_vec())?;
|
||||
|
||||
match total {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Bytes(num)),
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
let number = Number::Decimal(result);
|
||||
let number = convert_number_to_u64(&number);
|
||||
Ok(UntaggedValue::bytes(number).into_value(name))
|
||||
}
|
||||
Ok(_) => Err(ShellError::labeled_error(
|
||||
"could not calculate average of non-integer or unrelated types",
|
||||
"source",
|
||||
name,
|
||||
)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(other),
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"could not calculate average of non-integer or unrelated types",
|
||||
"source",
|
||||
name,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Average;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Average {})
|
||||
}
|
||||
}
|
@ -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 {})
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for pipeline in &block.block {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
loop {
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut iter = commands.list.clone().into_iter().peekable();
|
||||
loop {
|
||||
let item: Option<ClassifiedCommand> = iter.next();
|
||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
||||
|
||||
input = match (item, next) {
|
||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
}
|
||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
||||
|
||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
}
|
||||
|
||||
(None, _) => break,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
use crate::commands::command::whole_stream_command;
|
||||
use crate::commands::run_alias::AliasCommand;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
use log::{log_enabled, trace};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::InternalCommand;
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
}
|
||||
|
||||
let scope = Scope {
|
||||
it: it.clone(),
|
||||
vars: vars.clone(),
|
||||
env: env.clone(),
|
||||
};
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
let mut result = {
|
||||
context
|
||||
.run_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&scope,
|
||||
objects,
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let mut context = context.clone();
|
||||
// let scope = scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut soft_errs: Vec<ShellError> = vec![];
|
||||
let mut yielded = false;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err);
|
||||
break;
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: command.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: scope.clone(),
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry).await;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> = result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value { value: UntaggedValue::Table(list), ..})) => {
|
||||
for l in list {
|
||||
yield Ok(l);
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => {
|
||||
yield Ok(value.into_value(contents_tag.clone()));
|
||||
}
|
||||
Err(e) => yield Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(tagged_contents)
|
||||
}
|
||||
}
|
||||
CommandAction::EnterHelpShell(value) => {
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
)?,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
HelpShell::index(&context.registry())?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
FilesystemShell::with_location(location, context.registry().clone())?,
|
||||
));
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(AliasCommand::new(
|
||||
name,
|
||||
args,
|
||||
block,
|
||||
))
|
||||
]);
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
..
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
yield Err(err);
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => {
|
||||
yielded = true;
|
||||
yield Ok(v);
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
yielded = true;
|
||||
|
||||
let doc = PrettyDebug::pretty_doc(&v);
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
|
||||
let _ = doc.render_raw(
|
||||
context.with_host(|host| host.width() - 5),
|
||||
&mut nu_source::TermColored::new(&mut buffer),
|
||||
);
|
||||
|
||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||
|
||||
yield Ok(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_input_stream())
|
||||
}
|
@ -1,433 +0,0 @@
|
||||
use crate::commands::help::get_help;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir;
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct UnevaluatedCallInfo {
|
||||
pub args: hir::Call,
|
||||
pub name_tag: Tag,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn evaluate_with_new_it(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
) -> Result<CallInfo, ShellError> {
|
||||
let mut scope = self.scope.clone();
|
||||
scope.it = it.clone();
|
||||
let args = evaluate_args(&self.args, registry, &scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn switch_present(&self, switch: &str) -> bool {
|
||||
self.args.switch_preset(switch)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct CommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
pub input: InputStream,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
#[derive(Getters, Clone)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct RawCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
}
|
||||
|
||||
impl RawCommandArgs {
|
||||
pub fn with_input(self, input: impl Into<InputStream>) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host,
|
||||
ctrl_c: self.ctrl_c,
|
||||
current_errors: self.current_errors,
|
||||
shell_manager: self.shell_manager,
|
||||
call_info: self.call_info,
|
||||
input: input.into(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CommandArgs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.call_info.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub async fn evaluate_once(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
) -> 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 = self.call_info.evaluate(registry).await?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn evaluate_once_with_scope(
|
||||
self,
|
||||
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 mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
|
||||
Ok((T::deserialize(&mut deserializer)?, args.input))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EvaluatedWholeStreamCommandArgs {
|
||||
pub args: EvaluatedCommandArgs,
|
||||
pub input: InputStream,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedWholeStreamCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedWholeStreamCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
input: impl Into<InputStream>,
|
||||
) -> EvaluatedWholeStreamCommandArgs {
|
||||
EvaluatedWholeStreamCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
},
|
||||
input: input.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_tag(&self) -> Tag {
|
||||
self.args.call_info.name_tag.clone()
|
||||
}
|
||||
|
||||
pub fn parts(self) -> (InputStream, EvaluatedArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args.call_info.args)
|
||||
}
|
||||
|
||||
pub fn split(self) -> (InputStream, EvaluatedCommandArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub"]
|
||||
pub struct EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedFilterCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedFilterCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
) -> EvaluatedFilterCommandArgs {
|
||||
EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct EvaluatedCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: CallInfo,
|
||||
}
|
||||
|
||||
impl EvaluatedCommandArgs {
|
||||
pub fn nth(&self, pos: usize) -> Option<&Value> {
|
||||
self.call_info.args.nth(pos)
|
||||
}
|
||||
|
||||
/// Get the nth positional argument, error if not possible
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||
match self.call_info.args.nth(pos) {
|
||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
||||
Some(item) => Ok(item),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||
self.call_info.args.get(name)
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.call_info.args.has(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Example {
|
||||
pub example: &'static str,
|
||||
pub description: &'static str,
|
||||
pub result: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WholeStreamCommand: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name()).desc(self.usage()).filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||
|
||||
impl PrettyDebugWithSource for Command {
|
||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||
b::typed(
|
||||
"whole stream command",
|
||||
b::description(self.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ self.signature().pretty_debug(source),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Command {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Command({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn name(&self) -> &str {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> Signature {
|
||||
self.0.signature()
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> &str {
|
||||
self.0.usage()
|
||||
}
|
||||
|
||||
pub async fn run(&self, args: CommandArgs, registry: &CommandRegistry) -> OutputStream {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
let registry = registry.clone();
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
match self.0.run(args, registry).await {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => OutputStream::one(Err(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_binary(&self) -> bool {
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnFilterCommand {
|
||||
name: String,
|
||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FnFilterCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"usage"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
CommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
mut input,
|
||||
..
|
||||
}: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let host: Arc<parking_lot::Mutex<dyn Host>> = host.clone();
|
||||
let registry: CommandRegistry = registry.clone();
|
||||
let func = self.func;
|
||||
|
||||
let stream = async_stream! {
|
||||
while let Some(it) = input.next().await {
|
||||
let registry = registry.clone();
|
||||
let call_info = match call_info.clone().evaluate_with_new_it(®istry, &it).await {
|
||||
Err(err) => { yield Err(err); return; },
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => yield Err(err),
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.values.next().await {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
@ -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 {})
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
"count"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("count")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show the total number of rows or items."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let rows: Vec<Value> = args.input.collect().await;
|
||||
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::int(rows.len()).into_value(name),
|
||||
))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Count {})
|
||||
}
|
||||
}
|
@ -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 {})
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
||||
SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Each;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Each {
|
||||
fn name(&self) -> &str {
|
||||
"each"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each row",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
each(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Echo the square of each integer",
|
||||
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
UntaggedValue::int(9).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Echo the sum of each row",
|
||||
example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(7).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||
match &*head {
|
||||
SpannedExpression {
|
||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||
..
|
||||
} if s == "expanded-each" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<Context>,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input_clone = input.clone();
|
||||
let input_stream = if is_expanded_it_usage(&head) {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
};
|
||||
Ok(run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
async fn each(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
async {
|
||||
match process_row(block, scope, head, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Each;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Each {})
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{
|
||||
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Echo;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Echo {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Echo the arguments back to the user."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
echo(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Put a hello message in the pipeline",
|
||||
example: "echo 'hello'",
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Print the value of the special '$nu' variable",
|
||||
example: "echo $nu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||
|
||||
for i in args.rest {
|
||||
match i.as_string() {
|
||||
Ok(s) => {
|
||||
yield Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||
));
|
||||
}
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => {
|
||||
for value in table {
|
||||
yield Ok(ReturnSuccess::Value(value.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag
|
||||
} => {
|
||||
let mut current = range.from.0.item;
|
||||
while current != range.to.0.item {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
yield Err(ShellError::unimplemented("Internal error: expected a primitive result from increment"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
yield Err(ShellError::coerce_error(
|
||||
left_type.spanned(tag.span),
|
||||
right_type.spanned(tag.span),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
match range.to.1 {
|
||||
RangeInclusion::Inclusive => {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
yield Ok(ReturnSuccess::Value(i.clone()));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Echo {})
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Enter;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnterArgs {
|
||||
location: Tagged<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Enter {
|
||||
fn name(&self) -> &str {
|
||||
"enter"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("enter").required(
|
||||
"location",
|
||||
SyntaxShape::Path,
|
||||
"the location to create a new shell from",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a new shell and begin at this path."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
enter(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Enter a path as a new shell",
|
||||
example: "enter ../projectB",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Enter a file as a new shell",
|
||||
example: "enter package.json",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn enter(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location }, _) = raw_args.process(®istry).await?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
|
||||
if registry.has(command) {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterHelpShell(
|
||||
UntaggedValue::nothing().into_value(Tag::unknown()),
|
||||
)));
|
||||
} else if location.is_dir() {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterShell(
|
||||
location_clone,
|
||||
)));
|
||||
} else {
|
||||
// If it's a file, attempt to open the file as a value and enter it
|
||||
let cwd = shell_manager.path();
|
||||
|
||||
let full_path = std::path::PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents, contents_tag) =
|
||||
crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
tag.span,
|
||||
).await?;
|
||||
|
||||
match contents {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
let tagged_contents = contents.into_value(&contents_tag);
|
||||
|
||||
if let Some(extension) = file_extension {
|
||||
let command_name = format!("from {}", extension);
|
||||
if let Some(converter) =
|
||||
registry.get_command(&command_name)
|
||||
{
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
scope: scope.clone()
|
||||
},
|
||||
};
|
||||
let mut result = converter.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
®istry,
|
||||
).await;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
||||
Value {
|
||||
value,
|
||||
tag: contents_tag.clone(),
|
||||
})));
|
||||
}
|
||||
x => yield x,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let tagged_contents = contents.into_value(contents_tag);
|
||||
|
||||
yield Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(tagged_contents)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Enter;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
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,145 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub struct FromJSON;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FromJSONArgs {
|
||||
objects: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromJSON {
|
||||
fn name(&self) -> &str {
|
||||
"from json"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from json").switch(
|
||||
"objects",
|
||||
"treat each line as a separate value",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .json and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_json(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
match v {
|
||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
|
||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
||||
}
|
||||
serde_hjson::Value::Array(a) => UntaggedValue::Table(
|
||||
a.iter()
|
||||
.map(|x| convert_json_value_to_nu_value(x, &tag))
|
||||
.collect(),
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_hjson::Value::Object(o) => {
|
||||
let mut collected = TaggedDictBuilder::new(&tag);
|
||||
for (k, v) in o.iter() {
|
||||
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
|
||||
}
|
||||
|
||||
collected.into_value()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson::Result<Value> {
|
||||
let v: serde_hjson::Value = serde_hjson::from_str(&s)?;
|
||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
fn from_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (FromJSONArgs { objects }, mut input) = args.process(®istry).await?;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
|
||||
if objects {
|
||||
for json_str in concat_string.item.lines() {
|
||||
if json_str.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match from_json_string_to_value(json_str.to_string(), &name_tag) {
|
||||
Ok(x) =>
|
||||
yield ReturnSuccess::value(x),
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
"input cannot be parsed as JSON",
|
||||
&name_tag,
|
||||
"value originates from here",
|
||||
concat_string.tag.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match from_json_string_to_value(concat_string.item, name_tag.clone()) {
|
||||
Ok(x) =>
|
||||
match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
}
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
"input cannot be parsed as JSON",
|
||||
name_tag,
|
||||
"value originates from here",
|
||||
concat_string.tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromJSON;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromJSON {})
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
||||
pub struct FromODS;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FromODSArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromODS {
|
||||
fn name(&self) -> &str {
|
||||
"from ods"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from ods").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse OpenDocument Spreadsheet(.ods) data and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ods(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ods(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (FromODSArgs { headerless: _headerless }, mut input) = args.process(®istry).await?;
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
||||
let mut ods = Ods::<_>::new(buf).map_err(|_| ShellError::labeled_error(
|
||||
"Could not load ods file",
|
||||
"could not load ods file",
|
||||
&tag))?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let sheet_names = ods.sheet_names().to_owned();
|
||||
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
|
||||
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag));
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromODS;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromODS {})
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
||||
pub struct FromXLSX;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FromXLSXArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromXLSX {
|
||||
fn name(&self) -> &str {
|
||||
"from xlsx"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from xlsx").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse binary Excel(.xlsx) data and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_xlsx(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_xlsx(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (FromXLSXArgs { headerless: _headerless }, mut input) = args.process(®istry).await?;
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
let mut buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
let mut xls = Xlsx::<_>::new(buf).map_err(|_| {
|
||||
ShellError::labeled_error("Could not load xlsx file", "could not load xlsx file", &tag)
|
||||
})?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let sheet_names = xls.sheet_names().to_owned();
|
||||
|
||||
for sheet_name in &sheet_names {
|
||||
let mut sheet_output = TaggedListBuilder::new(&tag);
|
||||
|
||||
if let Some(Ok(current_sheet)) = xls.worksheet_range(sheet_name) {
|
||||
for row in current_sheet.rows() {
|
||||
let mut row_output = TaggedDictBuilder::new(&tag);
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
};
|
||||
|
||||
row_output.insert_untagged(&format!("Column{}", i), value);
|
||||
}
|
||||
|
||||
sheet_output.push_untagged(row_output.into_untagged_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged(sheet_name, sheet_output.into_untagged_value());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load sheet",
|
||||
"could not load sheet",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromXLSX;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXLSX {})
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub struct FromYAML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromYAML {
|
||||
fn name(&self) -> &str {
|
||||
"from yaml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from yaml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FromYML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromYML {
|
||||
fn name(&self) -> &str {
|
||||
"from yml"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from yml")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_yaml_value_to_nu_value(
|
||||
v: &serde_yaml::Value,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
Ok(match v {
|
||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
"expected a compatible number",
|
||||
&tag,
|
||||
)
|
||||
})?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
let result: Result<Vec<Value>, ShellError> = a
|
||||
.iter()
|
||||
.map(|x| convert_yaml_value_to_nu_value(x, &tag))
|
||||
.collect();
|
||||
UntaggedValue::Table(result?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Mapping(t) => {
|
||||
let mut collected = TaggedDictBuilder::new(&tag);
|
||||
|
||||
for (k, v) in t.iter() {
|
||||
match k {
|
||||
serde_yaml::Value::String(k) => {
|
||||
collected.insert_value(k.clone(), convert_yaml_value_to_nu_value(v, &tag)?);
|
||||
}
|
||||
_ => unimplemented!("Unknown key type"),
|
||||
}
|
||||
}
|
||||
|
||||
collected.into_value()
|
||||
}
|
||||
serde_yaml::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(tag),
|
||||
x => unimplemented!("Unsupported yaml case: {:?}", x),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_yaml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let v: serde_yaml::Value = serde_yaml::from_str(&s).map_err(|x| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load yaml: {}", x),
|
||||
"could not load yaml from text",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
Ok(convert_yaml_value_to_nu_value(&v, tag)?)
|
||||
}
|
||||
|
||||
async fn from_yaml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
|
||||
match from_yaml_string_to_value(concat_string.item, tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(futures::stream::iter(list).to_output_stream()),
|
||||
x => Ok(OutputStream::one(x)),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error_with_secondary(
|
||||
"Could not parse as YAML",
|
||||
"input cannot be parsed as YAML",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
&concat_string.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromYAML;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromYAML {})
|
||||
}
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::set::IndexSet;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
pub struct Get;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Get {
|
||||
fn name(&self) -> &str {
|
||||
"get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("get").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally return additional data by path",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Open given cells as text."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
get(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Extract the name of files as a list",
|
||||
example: "ls | get name",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Extract the cpu list from the sys information",
|
||||
example: "sys | get cpu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
let fields = path.clone();
|
||||
|
||||
get_data_by_column_path(
|
||||
obj,
|
||||
path,
|
||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
||||
let path_members_span = span_for_spanned_list(fields.members().iter().map(|p| p.span));
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
for row in rows {
|
||||
for field in row.data_descriptors() {
|
||||
if !existing_columns.contains(&field[..]) {
|
||||
existing_columns.insert(field.clone());
|
||||
names.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
"Appears to contain rows. Try indexing instead.",
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
} else {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions
|
||||
.iter()
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
};
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
let total = rows.len();
|
||||
|
||||
let secondary_label = if total == 1 {
|
||||
"The table only has 1 row".to_owned()
|
||||
} else {
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
format!("There isn't a row indexed at {}", idx),
|
||||
column_path_tried.span,
|
||||
secondary_label,
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
},
|
||||
UntaggedValue::Row(columns) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
&obj_source.data_descriptors().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"No rows available",
|
||||
format!("A row at '{}' can't be indexed.", &idx),
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Appears to contain columns. Columns available: {}",
|
||||
columns.keys().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
|
||||
error
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let mut vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
for desc in descs {
|
||||
yield ReturnSuccess::value(desc);
|
||||
}
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
while let Some(item) = input.next().await {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
yield ReturnSuccess::value(item.clone());
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => yield ReturnSuccess::value(other.clone()),
|
||||
},
|
||||
Err(reason) => yield ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
}
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct GroupBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByArgs {
|
||||
column_name: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupBy {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group-by").optional(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to group by",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the table rows grouped by the column given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
group_by(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Group items by type",
|
||||
example: r#"ls | group-by type"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Group items by their value",
|
||||
example: "echo [1 3 1 3 2 1 1] | group-by",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"1".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
]).into(),
|
||||
|
||||
"3".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]).into(),
|
||||
|
||||
"2".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
]).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn group_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (GroupByArgs { column_name }, 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 {
|
||||
match crate::utils::data::group(column_name, &values, None, &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(
|
||||
column_name: &Tagged<String>,
|
||||
values: Vec<Value>,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
crate::utils::data::group(Some(column_name.clone()), &values, None, tag)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::commands::group_by::group;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::*;
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
UntaggedValue::row(entries).into_untagged_value()
|
||||
}
|
||||
|
||||
fn table(list: &[Value]) -> Value {
|
||||
UntaggedValue::table(list).into_untagged_value()
|
||||
}
|
||||
|
||||
fn nu_releases_commiters() -> Vec<Value> {
|
||||
vec![
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||
let for_key = String::from("date").tagged_unknown();
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_country_column() -> Result<(), ShellError> {
|
||||
let for_key = String::from("country").tagged_unknown();
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"EC".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
]),
|
||||
"NZ".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
"US".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")}),
|
||||
]),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::GroupBy;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupBy {})
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct GroupByDate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByDateArgs {
|
||||
column_name: Option<Tagged<String>>,
|
||||
format: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupByDate {
|
||||
fn name(&self) -> &str {
|
||||
"group-by date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group-by date")
|
||||
.optional(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to group by",
|
||||
)
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the table rows grouped by the column given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
group_by_date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Group files by type",
|
||||
example: "ls | group-by date --format '%d/%m/%Y'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
enum Grouper {
|
||||
ByDate(Option<String>),
|
||||
}
|
||||
|
||||
pub async fn group_by_date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (
|
||||
GroupByDateArgs {
|
||||
column_name,
|
||||
format,
|
||||
},
|
||||
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 grouper = if let Some(Tagged { item: fmt, tag: _ }) = format {
|
||||
Grouper::ByDate(Some(fmt))
|
||||
} else {
|
||||
Grouper::ByDate(None)
|
||||
};
|
||||
|
||||
match grouper {
|
||||
Grouper::ByDate(None) => {
|
||||
match crate::utils::data::group(
|
||||
column_name,
|
||||
&values,
|
||||
Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))),
|
||||
&name,
|
||||
) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Grouper::ByDate(Some(fmt)) => {
|
||||
match crate::utils::data::group(
|
||||
column_name,
|
||||
&values,
|
||||
Some(Box::new(move |row: &Value| row.format(&fmt))),
|
||||
&name,
|
||||
) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GroupByDate;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupByDate {})
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Dictionary;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Headers;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Headers {
|
||||
fn name(&self) -> &str {
|
||||
"headers"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("headers")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use the first row of the table as column names"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
headers(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut input = args.input;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
if rows.len() < 1 {
|
||||
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?"));
|
||||
}
|
||||
|
||||
//the headers are the first row in the table
|
||||
let headers: Vec<String> = match &rows[0].value {
|
||||
UntaggedValue::Row(d) => {
|
||||
Ok(d.entries.iter().map(|(k, v)| {
|
||||
match v.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => { //If a cell that should contain a header name is empty, we name the column Column[index]
|
||||
match d.entries.get_full(k) {
|
||||
Some((index, _, _)) => format!("Column{}", index),
|
||||
None => "unknownColumn".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
_ => Err(ShellError::unexpected_eof("Could not get headers, is the table empty?", rows[0].tag.span))
|
||||
}?;
|
||||
|
||||
//Each row is a dictionary with the headers as keys
|
||||
for r in rows.iter().skip(1) {
|
||||
match &r.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let mut i = 0;
|
||||
let mut entries = IndexMap::new();
|
||||
for (_, v) in d.entries.iter() {
|
||||
entries.insert(headers[i].clone(), v.clone());
|
||||
i += 1;
|
||||
}
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::Row(Dictionary{entries}).into_value(r.tag.clone())))
|
||||
}
|
||||
_ => yield Err(ShellError::unexpected_eof("Couldn't iterate through rows, was the input a properly formatted table?", r.tag.span))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Headers;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Headers {})
|
||||
}
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
|
||||
UntaggedValue,
|
||||
};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct Help;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HelpArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about commands."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
help(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let stream = async_stream! {
|
||||
let (HelpArgs { rest }, mut input) = args.process(®istry).await?;
|
||||
if let Some(document) = rest.get(0) {
|
||||
if document.item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
for cmd in sorted_names {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
continue;
|
||||
}
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let document_tag = document.tag.clone();
|
||||
let value = command_dict(
|
||||
registry.get_command(&cmd).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
})?,
|
||||
name.clone(),
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
get_data_by_key(&value, "usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
|
||||
yield ReturnSuccess::value(short_desc.into_value());
|
||||
}
|
||||
} else if rest.len() == 2 {
|
||||
// Check for a subcommand
|
||||
let command_name = format!("{} {}", rest[0].item, rest[1].item);
|
||||
if let Some(command) = registry.get_command(&command_name) {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), ®istry)).into_value(Tag::unknown())));
|
||||
}
|
||||
} else if let Some(command) = registry.get_command(&document.item) {
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::string(get_help(command.stream_command(), ®istry)).into_value(Tag::unknown())));
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
document.tag.span,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help commands - list all available commands
|
||||
* help <command name> - display help about a particular command
|
||||
|
||||
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||
|
||||
[Examples]
|
||||
|
||||
List the files in the current directory, sorted by size:
|
||||
ls | sort-by size
|
||||
|
||||
Get information about the current system:
|
||||
sys | get host
|
||||
|
||||
Get the processes on your system actively using CPU:
|
||||
ps | where cpu > 0
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
yield Ok(ReturnSuccess::Value(UntaggedValue::string(msg).into_value(Tag::unknown())));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
|
||||
let cmd_name = cmd.name();
|
||||
let signature = cmd.signature();
|
||||
let mut long_desc = String::new();
|
||||
|
||||
long_desc.push_str(&cmd.usage());
|
||||
long_desc.push_str("\n");
|
||||
|
||||
let mut subcommands = String::new();
|
||||
for name in registry.names() {
|
||||
if name.starts_with(&format!("{} ", cmd_name)) {
|
||||
let subcommand = registry.get_command(&name).expect("This shouldn't happen");
|
||||
|
||||
subcommands.push_str(&format!(" {} - {}\n", name, subcommand.usage()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut one_liner = String::new();
|
||||
one_liner.push_str(&signature.name);
|
||||
one_liner.push_str(" ");
|
||||
|
||||
for positional in &signature.positional {
|
||||
match &positional.0 {
|
||||
PositionalType::Mandatory(name, _m) => {
|
||||
one_liner.push_str(&format!("<{}> ", name));
|
||||
}
|
||||
PositionalType::Optional(name, _o) => {
|
||||
one_liner.push_str(&format!("({}) ", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if signature.rest_positional.is_some() {
|
||||
one_liner.push_str(" ...args");
|
||||
}
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
one_liner.push_str("<subcommand> ");
|
||||
}
|
||||
|
||||
if !signature.named.is_empty() {
|
||||
one_liner.push_str("{flags} ");
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
long_desc.push_str("\nSubcommands:\n");
|
||||
long_desc.push_str(&subcommands);
|
||||
}
|
||||
|
||||
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
|
||||
long_desc.push_str("\nParameters:\n");
|
||||
for positional in &signature.positional {
|
||||
match &positional.0 {
|
||||
PositionalType::Mandatory(name, _m) => {
|
||||
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
|
||||
}
|
||||
PositionalType::Optional(name, _o) => {
|
||||
long_desc.push_str(&format!(" ({}) {}\n", name, positional.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &signature.rest_positional {
|
||||
long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1));
|
||||
}
|
||||
}
|
||||
if !signature.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(&signature))
|
||||
}
|
||||
|
||||
let palette = crate::shell::palette::DefaultPalette {};
|
||||
let examples = cmd.examples();
|
||||
if !examples.is_empty() {
|
||||
long_desc.push_str("\nExamples:");
|
||||
}
|
||||
for example in examples {
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push_str(" ");
|
||||
long_desc.push_str(example.description);
|
||||
let colored_example =
|
||||
crate::shell::helper::Painter::paint_string(example.example, registry, &palette);
|
||||
long_desc.push_str(&format!("\n > {}\n", colored_example));
|
||||
}
|
||||
|
||||
long_desc.push_str("\n");
|
||||
|
||||
long_desc
|
||||
}
|
||||
|
||||
fn get_flags_section(signature: &Signature) -> String {
|
||||
let mut long_desc = String::new();
|
||||
long_desc.push_str("\nFlags:\n");
|
||||
for (flag, ty) in &signature.named {
|
||||
let msg = match ty.0 {
|
||||
NamedType::Switch(s) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{}{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{}{} {}\n",
|
||||
flag,
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Mandatory(s, m) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}> (required parameter){} {}\n",
|
||||
c,
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}> (required parameter){} {}\n",
|
||||
flag,
|
||||
m.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
NamedType::Optional(s, o) => {
|
||||
if let Some(c) = s {
|
||||
format!(
|
||||
" -{}, --{} <{}>{} {}\n",
|
||||
c,
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" --{} <{}>{} {}\n",
|
||||
flag,
|
||||
o.display(),
|
||||
if !ty.1.is_empty() { ":" } else { "" },
|
||||
ty.1
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
long_desc.push_str(&msg);
|
||||
}
|
||||
long_desc
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Help;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Help {})
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
use crate::commands::group_by::group;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{columns_sorted, evaluate, map_max, reduce, t_sort};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
|
||||
pub struct Histogram;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HistogramArgs {
|
||||
column_name: Tagged<String>,
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Histogram {
|
||||
fn name(&self) -> &str {
|
||||
"histogram"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("histogram")
|
||||
.required(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to graph by",
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::String,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with a histogram based on the column name passed in."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
histogram(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a histogram for the types of files",
|
||||
example: "ls | histogram type",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Get a histogram for the types of files, with frequency column named count",
|
||||
example: "ls | histogram type count",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get a histogram for a list of numbers",
|
||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn histogram(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let stream = async_stream! {
|
||||
let (HistogramArgs { column_name, rest}, mut input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let Tagged { item: group_by, .. } = column_name.clone();
|
||||
|
||||
let groups = group(&column_name, values, &name)?;
|
||||
let group_labels = columns_sorted(Some(group_by.clone()), &groups, &name);
|
||||
let sorted = t_sort(Some(group_by.clone()), None, &groups, &name)?;
|
||||
let evaled = evaluate(&sorted, None, &name)?;
|
||||
let reduced = reduce(&evaled, None, &name)?;
|
||||
let maxima = map_max(&reduced, None, &name)?;
|
||||
let percents = percentages(&reduced, maxima, &name)?;
|
||||
|
||||
match percents {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let column = (*column_name).clone();
|
||||
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error("Unable to load group count", "unabled to load group count", &name);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
yield Err(count_shell_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
yield Err(count_shell_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Value { value: UntaggedValue::Table(start), .. } = datasets.get(0).ok_or_else(|| ShellError::labeled_error("Unable to load dataset", "unabled to load dataset", &name))? {
|
||||
for percentage in start.iter() {
|
||||
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let value: Tagged<String> = group_labels.get(idx).ok_or_else(|| ShellError::labeled_error("Unable to load group labels", "unabled to load group labels", &name))?.clone();
|
||||
fact.insert_value(&column, UntaggedValue::string(value.item).into_value(value.tag));
|
||||
|
||||
fact.insert_untagged(&count_column_name, UntaggedValue::int(count_values[idx]));
|
||||
|
||||
if let Value { value: UntaggedValue::Primitive(Primitive::Int(ref num)), ref tag } = percentage.clone() {
|
||||
let string = std::iter::repeat("*").take(num.to_i32().ok_or_else(|| ShellError::labeled_error("Expected a number", "expected a number", tag))? as usize).collect::<String>();
|
||||
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
|
||||
yield ReturnSuccess::value(fact.into_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let results: Value = match values {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let datasets: Vec<_> = datasets
|
||||
.iter()
|
||||
.map(|subsets| match subsets {
|
||||
Value {
|
||||
value: UntaggedValue::Table(data),
|
||||
..
|
||||
} => {
|
||||
let data = data
|
||||
.iter()
|
||||
.map(|d| match d {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
let max = match &max {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(maxima)),
|
||||
..
|
||||
} => maxima.clone(),
|
||||
_ => Zero::zero(),
|
||||
};
|
||||
|
||||
let n = (n * 100) / max;
|
||||
|
||||
UntaggedValue::int(n).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::int(0).into_value(&tag),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
UntaggedValue::Table(data).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::Table(vec![]).into_value(&tag),
|
||||
})
|
||||
.collect();
|
||||
|
||||
UntaggedValue::Table(datasets).into_value(&tag)
|
||||
}
|
||||
other => other.clone(),
|
||||
};
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Histogram;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Histogram {})
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
use crate::cli::History as HistoryFile;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub struct History;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for History {
|
||||
fn name(&self) -> &str {
|
||||
"history"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("history")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display command history."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
history(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag;
|
||||
let stream = async_stream! {
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
if let Ok(line) = line {
|
||||
yield ReturnSuccess::value(UntaggedValue::string(line).into_value(tag.clone()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Could not open history", "history file could not be opened", tag.clone()));
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Insert;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct InsertArgs {
|
||||
column: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Insert {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("insert")
|
||||
.required(
|
||||
"column",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column name to insert",
|
||||
)
|
||||
.required(
|
||||
"value",
|
||||
SyntaxShape::String,
|
||||
"the value to give the cell(s)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Insert a new column with a given value."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
insert(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (InsertArgs { column, value }, mut input) = args.process(®istry).await?;
|
||||
while let Some(row) = input.next().await {
|
||||
match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match row.insert_data_at_column_path(&column, value.clone()) {
|
||||
Ok(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
Err(err) => yield Err(err),
|
||||
},
|
||||
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Insert;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Insert {})
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
enum IsEmptyFor {
|
||||
Value,
|
||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
||||
RowWithField(Tagged<ColumnPath>),
|
||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
||||
}
|
||||
|
||||
pub struct IsEmpty;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IsEmptyArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IsEmpty {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness followed by the replacement value.",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (IsEmptyArgs { rest }, mut input) = args.process(®istry).await?;
|
||||
while let Some(value) = input.next().await {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if rest.len() <= 2 {
|
||||
let field = rest.get(0);
|
||||
let replacement_if_true = rest.get(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Some(field), Some(replacement_if_true)) => {
|
||||
IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
)
|
||||
}
|
||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => yield Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
)),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val =
|
||||
crate::commands::get::get_column_path(&field, &out)?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default.clone()) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
};
|
||||
|
||||
out = emptiness_value?;
|
||||
}
|
||||
|
||||
yield Ok(ReturnSuccess::Value(out))
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val =
|
||||
crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => yield Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val =
|
||||
crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => yield Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
yield Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IsEmpty;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IsEmpty {})
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct KeepUntil;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for KeepUntil {
|
||||
fn name(&self) -> &str {
|
||||
"keep-until"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-until")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to stop keeping rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows until the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let stream = async_stream! {
|
||||
let mut call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
yield ReturnSuccess::value(item);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KeepUntil;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(KeepUntil {})
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct KeepWhile;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for KeepWhile {
|
||||
fn name(&self) -> &str {
|
||||
"keep-while"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-while")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to keep rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows while the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let stream = async_stream! {
|
||||
let mut call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
yield ReturnSuccess::value(item);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KeepWhile;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(KeepWhile {})
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub struct Kill;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct KillArgs {
|
||||
pub pid: Tagged<u64>,
|
||||
pub rest: Vec<Tagged<u64>>,
|
||||
pub force: Tagged<bool>,
|
||||
pub quiet: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Kill {
|
||||
fn name(&self) -> &str {
|
||||
"kill"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("kill")
|
||||
.required(
|
||||
"pid",
|
||||
SyntaxShape::Int,
|
||||
"process id of process that is to be killed",
|
||||
)
|
||||
.rest(SyntaxShape::Int, "rest of processes to kill")
|
||||
.switch("force", "forcefully kill the process", Some('f'))
|
||||
.switch("quiet", "won't print anything to the console", Some('q'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Kill a process using the process id."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
kill(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Kill the pid using the most memory",
|
||||
example: "ps | sort-by mem | last | kill $it.pid",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Force kill a given pid",
|
||||
example: "kill --force 12345",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (KillArgs {
|
||||
pid,
|
||||
rest,
|
||||
force,
|
||||
quiet,
|
||||
}, mut input) = args.process(®istry).await?;
|
||||
let mut cmd = if cfg!(windows) {
|
||||
let mut cmd = Command::new("taskkill");
|
||||
|
||||
if *force {
|
||||
cmd.arg("/F");
|
||||
}
|
||||
|
||||
cmd.arg("/PID");
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
// each pid must written as `/PID 0` otherwise
|
||||
// taskkill will act as `killall` unix command
|
||||
for id in &rest {
|
||||
cmd.arg("/PID");
|
||||
cmd.arg(id.item().to_string());
|
||||
}
|
||||
|
||||
cmd
|
||||
} else {
|
||||
let mut cmd = Command::new("kill");
|
||||
|
||||
if *force {
|
||||
cmd.arg("-9");
|
||||
}
|
||||
|
||||
cmd.arg(pid.item().to_string());
|
||||
|
||||
cmd.args(rest.iter().map(move |id| id.item().to_string()));
|
||||
|
||||
cmd
|
||||
};
|
||||
|
||||
// pipe everything to null
|
||||
if *quiet {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
}
|
||||
|
||||
cmd.status().expect("failed to execute shell command");
|
||||
|
||||
if false {
|
||||
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Kill;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Kill {})
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Lines;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Lines {
|
||||
fn name(&self) -> &str {
|
||||
"lines"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("lines")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Split single string into rows, one per line."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
lines(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Split multi-line string into lines",
|
||||
example: r#"^echo "two\nlines" | lines"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn ends_with_line_ending(st: &str) -> bool {
|
||||
let mut temp = st.to_string();
|
||||
let last = temp.pop();
|
||||
if let Some(c) = last {
|
||||
c == '\n'
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let mut leftover = vec![];
|
||||
let mut leftover_string = String::new();
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let args = args.evaluate_once(®istry).await.unwrap();
|
||||
let tag = args.name_tag();
|
||||
let name_span = tag.span;
|
||||
let mut input = args.input;
|
||||
loop {
|
||||
match input.next().await {
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
|
||||
let mut st = leftover_string.clone() + &st;
|
||||
leftover.clear();
|
||||
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
|
||||
if !ends_with_line_ending(&st) {
|
||||
if let Some(last) = lines.pop() {
|
||||
leftover_string = last;
|
||||
} else {
|
||||
leftover_string.clear();
|
||||
}
|
||||
} else {
|
||||
leftover_string.clear();
|
||||
}
|
||||
|
||||
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
|
||||
|
||||
yield futures::stream::iter(success_lines)
|
||||
}
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::Line(st)), ..}) => {
|
||||
let mut st = leftover_string.clone() + &st;
|
||||
leftover.clear();
|
||||
|
||||
let mut lines: Vec<String> = st.lines().map(|x| x.to_string()).collect();
|
||||
if !ends_with_line_ending(&st) {
|
||||
if let Some(last) = lines.pop() {
|
||||
leftover_string = last;
|
||||
} else {
|
||||
leftover_string.clear();
|
||||
}
|
||||
} else {
|
||||
leftover_string.clear();
|
||||
}
|
||||
|
||||
let success_lines: Vec<_> = lines.iter().map(|x| ReturnSuccess::value(UntaggedValue::line(x).into_untagged_value())).collect();
|
||||
yield futures::stream::iter(success_lines)
|
||||
}
|
||||
Some( Value { tag: value_span, ..}) => {
|
||||
yield futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
))]);
|
||||
}
|
||||
None => {
|
||||
if !leftover.is_empty() {
|
||||
let mut st = leftover_string.clone();
|
||||
if let Ok(extra) = String::from_utf8(leftover) {
|
||||
st.push_str(&extra);
|
||||
}
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(st).into_untagged_value())])
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !leftover_string.is_empty() {
|
||||
yield futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::string(leftover_string).into_untagged_value())]);
|
||||
}
|
||||
}
|
||||
.flatten();
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Lines;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Lines {})
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::map_max;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
pub struct MapMaxBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MapMaxByArgs {
|
||||
column_name: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for MapMaxBy {
|
||||
fn name(&self) -> &str {
|
||||
"map-max-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("map-max-by").named(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to map-max the table's rows",
|
||||
Some('c'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the tables rows maxed by the column given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
map_max_by(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn map_max_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (MapMaxByArgs { column_name }, 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 map_by_column = if let Some(column_to_map) = column_name {
|
||||
Some(column_to_map.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match map_max(&values[0], map_by_column, name) {
|
||||
Ok(table_maxed) => Ok(OutputStream::one(ReturnSuccess::value(table_maxed))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::MapMaxBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(MapMaxBy {})
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::value::merge_values;
|
||||
use crate::prelude::*;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
pub struct Merge;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MergeArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Merge {
|
||||
fn name(&self) -> &str {
|
||||
"merge"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("merge").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and merge into the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Merge a table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
merge(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Merge a 1-based index column with some ls output",
|
||||
example: "ls | select name | keep 3 | merge { echo [1 2 3] | wrap index }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let stream = async_stream! {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let name_tag = raw_args.call_info.name_tag.clone();
|
||||
let (merge_args, mut input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = merge_args.block;
|
||||
|
||||
let table: Option<Vec<Value>> = match run_block(&block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env).await {
|
||||
Ok(mut stream) => Some(stream.drain_vec().await),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let table = table.unwrap_or_else(|| vec![Value {
|
||||
value: UntaggedValue::row(IndexMap::default()),
|
||||
tag: name_tag,
|
||||
}]);
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
while let Some(value) = input.next().await {
|
||||
let other = table.get(idx);
|
||||
|
||||
match other {
|
||||
Some(replacement) => {
|
||||
match merge_values(&value.value, &replacement.value) {
|
||||
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
|
||||
Err(err) => {
|
||||
let message = format!("The row at {:?} types mismatch", idx);
|
||||
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => yield ReturnSuccess::value(value),
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Merge;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Merge {})
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::{AnchorLocation, Span, Tagged};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct Open;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct OpenArgs {
|
||||
path: Tagged<PathBuf>,
|
||||
raw: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Open {
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"path",
|
||||
SyntaxShape::Path,
|
||||
"the file path to load values from",
|
||||
)
|
||||
.switch(
|
||||
"raw",
|
||||
"load content as a string instead of a table",
|
||||
Some('r'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
open(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Opens \"users.csv\" and creates a table from the data",
|
||||
example: "open users.csv",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let cwd = PathBuf::from(args.shell_manager.path());
|
||||
let full_path = cwd;
|
||||
let registry = registry.clone();
|
||||
|
||||
let (OpenArgs { path, raw }, _) = args.process(®istry).await?;
|
||||
let result = fetch(&full_path, &path.item, path.tag.span).await;
|
||||
|
||||
let (file_extension, contents, contents_tag) = result?;
|
||||
|
||||
let file_extension = if raw.item {
|
||||
None
|
||||
} else {
|
||||
// If the extension could not be determined via mimetype, try to use the path
|
||||
// extension. Some file types do not declare their mimetypes (such as bson files).
|
||||
file_extension.or_else(|| path.extension().map(|x| x.to_string_lossy().to_string()))
|
||||
};
|
||||
|
||||
let tagged_contents = contents.into_value(&contents_tag);
|
||||
|
||||
if let Some(extension) = file_extension {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AutoConvert(tagged_contents, extension),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(tagged_contents)))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch(
|
||||
cwd: &PathBuf,
|
||||
location: &PathBuf,
|
||||
span: Span,
|
||||
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
|
||||
let mut cwd = cwd.clone();
|
||||
|
||||
cwd.push(Path::new(location));
|
||||
if let Ok(cwd) = dunce::canonicalize(cwd) {
|
||||
match std::fs::read(&cwd) {
|
||||
Ok(bytes) => match std::str::from_utf8(&bytes) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
|
||||
},
|
||||
)),
|
||||
Err(_) => {
|
||||
//Non utf8 data.
|
||||
match (bytes.get(0), bytes.get(1)) {
|
||||
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
|
||||
// Possibly UTF-16 little endian
|
||||
let utf16 = read_le_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
|
||||
// Possibly UTF-16 big endian
|
||||
let utf16 = read_be_u16(&bytes[2..]);
|
||||
|
||||
if let Some(utf16) = utf16 {
|
||||
match std::string::String::from_utf16(&utf16) {
|
||||
Ok(s) => Ok((
|
||||
cwd.extension()
|
||||
.map(|name| name.to_string_lossy().to_string()),
|
||||
UntaggedValue::string(s),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
Err(_) => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Ok((
|
||||
None,
|
||||
UntaggedValue::binary(bytes),
|
||||
Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
)),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"File could not be opened",
|
||||
"file not found",
|
||||
span,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"File could not be opened",
|
||||
"file not found",
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_le_u16(input: &[u8]) -> Option<Vec<u16>> {
|
||||
if input.len() % 2 != 0 || input.len() < 2 {
|
||||
None
|
||||
} else {
|
||||
let mut result = vec![];
|
||||
let mut pos = 0;
|
||||
while pos < input.len() {
|
||||
result.push(u16::from_le_bytes([input[pos], input[pos + 1]]));
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_be_u16(input: &[u8]) -> Option<Vec<u16>> {
|
||||
if input.len() % 2 != 0 || input.len() < 2 {
|
||||
None
|
||||
} else {
|
||||
let mut result = vec![];
|
||||
let mut pos = 0;
|
||||
while pos < input.len() {
|
||||
result.push(u16::from_be_bytes([input[pos], input[pos + 1]]));
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Open;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Open {})
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
merge_descriptors, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue,
|
||||
};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct Pivot;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PivotArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
#[serde(rename(deserialize = "header-row"))]
|
||||
header_row: bool,
|
||||
#[serde(rename(deserialize = "ignore-titles"))]
|
||||
ignore_titles: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Pivot {
|
||||
fn name(&self) -> &str {
|
||||
"pivot"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("pivot")
|
||||
.switch(
|
||||
"header-row",
|
||||
"treat the first row as column names",
|
||||
Some('r'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-titles",
|
||||
"don't pivot the column names into values",
|
||||
Some('i'),
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::String,
|
||||
"the names to give columns once pivoted",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Pivots the table contents so rows become columns and columns become rows."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pivot(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pivot(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let stream = async_stream! {
|
||||
let (args, mut input): (PivotArgs, _) = args.process(®istry).await?;
|
||||
let input = input.into_vec().await;
|
||||
|
||||
let descs = merge_descriptors(&input);
|
||||
|
||||
let mut headers: Vec<String> = vec![];
|
||||
|
||||
if args.rest.len() > 0 && args.header_row {
|
||||
yield Err(ShellError::labeled_error("Can not provide header names and use header row", "using header row", name));
|
||||
return;
|
||||
}
|
||||
|
||||
if args.header_row {
|
||||
for i in input.clone() {
|
||||
if let Some(desc) = descs.get(0) {
|
||||
match get_data_by_key(&i, desc[..].spanned_unknown()) {
|
||||
Some(x) => {
|
||||
if let Ok(s) = x.as_string() {
|
||||
headers.push(s.to_string());
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Header row needs string headers", "used non-string headers", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Header row is incomplete and can't be used", "using incomplete header row", name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in 0..=input.len() {
|
||||
if let Some(name) = args.rest.get(i) {
|
||||
headers.push(name.to_string())
|
||||
} else {
|
||||
headers.push(format!("Column{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let descs: Vec<_> = if args.header_row {
|
||||
descs.iter().skip(1).collect()
|
||||
} else {
|
||||
descs.iter().collect()
|
||||
};
|
||||
|
||||
for desc in descs {
|
||||
let mut column_num: usize = 0;
|
||||
let mut dict = TaggedDictBuilder::new(&name);
|
||||
|
||||
if !args.ignore_titles && !args.header_row {
|
||||
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::string(desc.clone()));
|
||||
column_num += 1
|
||||
}
|
||||
|
||||
for i in input.clone() {
|
||||
match get_data_by_key(&i, desc[..].spanned_unknown()) {
|
||||
Some(x) => {
|
||||
dict.insert_value(headers[column_num].clone(), x.clone());
|
||||
}
|
||||
_ => {
|
||||
dict.insert_untagged(headers[column_num].clone(), UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
column_num += 1;
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(dict.into_value());
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
Ok(OutputStream::new(stream))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Pivot;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Pivot {})
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JsonRpc<T> {
|
||||
jsonrpc: String,
|
||||
pub method: String,
|
||||
pub params: T,
|
||||
}
|
||||
|
||||
impl<T> JsonRpc<T> {
|
||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
||||
JsonRpc {
|
||||
jsonrpc: "2.0".into(),
|
||||
method: method.into(),
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "method")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum NuResult {
|
||||
response {
|
||||
params: Result<VecDeque<ReturnValue>, ShellError>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct PluginCommand {
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PluginCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
&self.config.usage
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
filter_plugin(self.path.clone(), args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_plugin(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
trace!("filter_plugin :: {}", path);
|
||||
let registry = registry.clone();
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
trace!("filtering :: {:?}", call_info);
|
||||
|
||||
// Beginning of the stream
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load json from plugin",
|
||||
"could not load json from plugin",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
yield Err(ShellError::unexpected(format!("{}", err)));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => for param in params { yield param },
|
||||
Err(e) => {
|
||||
yield ReturnValue::Err(e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing begin_filter response: {:?} {}",
|
||||
e, input
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while reading begin_filter response: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stream contents
|
||||
{
|
||||
while let Some(v) = args.input.next().await {
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request = JsonRpc::new("filter", v);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing filter response: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => for param in params { yield param },
|
||||
Err(e) => {
|
||||
yield ReturnValue::Err(e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing filter response: {:?}\n== input ==\n{}",
|
||||
e, input
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while reading filter response: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// post stream contents
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Could not load json from plugin",
|
||||
"could not load json from plugin",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
Ok(request_raw) => match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
yield Err(ShellError::unexpected(format!("{}", err)));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => for param in params { yield param },
|
||||
Err(e) => {
|
||||
yield ReturnValue::Err(e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing end_filter response: {:?} {}",
|
||||
e, input
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while reading end_filter response: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End of the stream
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error while processing quit response: {:?}",
|
||||
e
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = child.wait();
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct PluginSink {
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PluginSink {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
&self.config.usage
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sink_plugin(self.path.clone(), args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sink_plugin(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let request = JsonRpc::new("sink", (call_info.clone(), input));
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
if let Ok(request_raw) = request_raw {
|
||||
if let Ok(mut tmpfile) = tempfile::NamedTempFile::new() {
|
||||
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||
let _ = tmpfile.flush();
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
.arg(tmpfile.path())
|
||||
.spawn();
|
||||
|
||||
if let Ok(mut child) = child {
|
||||
let _ = child.wait();
|
||||
|
||||
// Needed for async_stream to type check
|
||||
if false {
|
||||
yield ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value());
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not create process for sink command"));
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not open file to send sink command message"));
|
||||
}
|
||||
} else {
|
||||
yield Err(ShellError::untagged_runtime_error("Could not create message to sink command"));
|
||||
}
|
||||
};
|
||||
Ok(OutputStream::new(stream))
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::NumericRange;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RangeArgs {
|
||||
area: Tagged<NumericRange>,
|
||||
}
|
||||
|
||||
pub struct Range;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Range {
|
||||
fn name(&self) -> &str {
|
||||
"range"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("range").required(
|
||||
"rows ",
|
||||
SyntaxShape::Range,
|
||||
"range of rows to return: Eg) 4..7 (=> from 4 to 7)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return only the selected rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
range(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (RangeArgs { area }, mut input) = args.process(®istry).await?;
|
||||
let range = area.item;
|
||||
let (from, _) = range.from;
|
||||
let (to, _) = range.to;
|
||||
|
||||
let from = *from as usize;
|
||||
let to = *to as usize;
|
||||
|
||||
let mut inp = input.skip(from).take(to - from + 1);
|
||||
while let Some(item) = inp.next().await {
|
||||
yield ReturnSuccess::value(item);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Range;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Range {})
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::reduce;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
pub struct ReduceBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ReduceByArgs {
|
||||
reduce_with: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ReduceBy {
|
||||
fn name(&self) -> &str {
|
||||
"reduce-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("reduce-by").named(
|
||||
"reduce_with",
|
||||
SyntaxShape::String,
|
||||
"the command to reduce by with",
|
||||
Some('w'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the tables rows reduced by the command given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
reduce_by(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reduce_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (ReduceByArgs { reduce_with }, mut input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
let reduce_with = if let Some(reducer) = reduce_with {
|
||||
Some(reducer.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match reduce(&values[0], reduce_with, name) {
|
||||
Ok(reduced) => Ok(OutputStream::one(ReturnSuccess::value(reduced))),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ReduceBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ReduceBy {})
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::base::reject_fields;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RejectArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
pub struct Reject;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Reject {
|
||||
fn name(&self) -> &str {
|
||||
"reject"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("reject").rest(SyntaxShape::String, "the names of columns to remove")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove the given columns from the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
reject(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Lists the files in a directory without showing the modified column",
|
||||
example: "ls | reject modified",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn reject(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (RejectArgs { rest: fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Reject requires fields",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
while let Some(item) = input.next().await {
|
||||
yield ReturnSuccess::value(reject_fields(&item, &fields, &item.tag));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Reject;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Reject {})
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape};
|
||||
|
||||
#[derive(new, Clone)]
|
||||
pub struct AliasCommand {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AliasCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
let mut alias = Signature::build(&self.name);
|
||||
|
||||
for arg in &self.args {
|
||||
alias = alias.optional(arg, SyntaxShape::Any, "");
|
||||
}
|
||||
|
||||
alias
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let call_info = args.call_info.clone();
|
||||
let registry = registry.clone();
|
||||
let block = self.block.clone();
|
||||
let alias_command = self.clone();
|
||||
let mut context = Context::from_args(&args, ®istry);
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut scope = call_info.scope.clone();
|
||||
let evaluated = call_info.evaluate(®istry).await?;
|
||||
if let Some(positional) = &evaluated.args.positional {
|
||||
for (pos, arg) in positional.iter().enumerate() {
|
||||
scope.vars.insert(alias_command.args[pos].to_string(), arg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(stream) if stream.is_empty() => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a block",
|
||||
"alias needs a block",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
Ok(mut stream) => {
|
||||
// We collect first to ensure errors are put into the context
|
||||
while let Some(result) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(result));
|
||||
}
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(x) = errors.first() {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Alias failed to run",
|
||||
"alias failed to run",
|
||||
tag.clone(),
|
||||
x.to_string(),
|
||||
tag
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Alias failed to run",
|
||||
"alias failed to run",
|
||||
tag.clone(),
|
||||
e.to_string(),
|
||||
tag
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::classified::external;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Expression, ExternalArgs, ExternalCommand, Literal, SpannedExpression};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RunExternalArgs {}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct RunExternalCommand {
|
||||
/// Whether or not nushell is being used in an interactive context
|
||||
pub(crate) interactive: bool,
|
||||
}
|
||||
|
||||
fn spanned_expression_to_string(expr: SpannedExpression) -> Result<String, ShellError> {
|
||||
if let SpannedExpression {
|
||||
expr: Expression::Literal(Literal::String(s)),
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected string for command name",
|
||||
"expected string",
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for RunExternalCommand {
|
||||
fn name(&self) -> &str {
|
||||
"run_external"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).rest(SyntaxShape::Any, "external command arguments")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let positionals = args.call_info.args.positional.clone().ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error("positional arguments unexpectedly empty")
|
||||
})?;
|
||||
|
||||
let mut positionals = positionals.into_iter();
|
||||
|
||||
let name = positionals
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error("run_external called with no arguments")
|
||||
})
|
||||
.and_then(spanned_expression_to_string)?;
|
||||
|
||||
let mut external_context = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(
|
||||
std::collections::HashMap::new(),
|
||||
)),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let is_interactive = self.interactive;
|
||||
|
||||
let stream = async_stream! {
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
name_tag: args.call_info.name_tag.clone(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
};
|
||||
|
||||
// If we're in interactive mode, we will "auto cd". That is, instead of interpreting
|
||||
// this as an external command, we will see it as a path and `cd` into it.
|
||||
if is_interactive {
|
||||
if let Some(path) = maybe_autocd_dir(&command, &mut external_context).await {
|
||||
let cd_args = CdArgs {
|
||||
path: Some(Tagged {
|
||||
item: PathBuf::from(path),
|
||||
tag: args.call_info.name_tag.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
let result = external_context.shell_manager.cd(cd_args, args.call_info.name_tag.clone());
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.next().await {
|
||||
yield value;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
let is_last = args.call_info.args.is_last;
|
||||
let input = args.input;
|
||||
let result = external::run_external_command(
|
||||
command,
|
||||
&mut external_context,
|
||||
input,
|
||||
&scope,
|
||||
is_last,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(value));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
async fn maybe_autocd_dir<'a>(cmd: &ExternalCommand, ctx: &mut Context) -> Option<String> {
|
||||
// We will "auto cd" if
|
||||
// - the command name ends in a path separator, or
|
||||
// - it's not a command on the path and no arguments were given.
|
||||
let name = &cmd.name;
|
||||
let path_name = if name.ends_with(std::path::MAIN_SEPARATOR)
|
||||
|| (cmd.args.is_empty()
|
||||
&& PathBuf::from(name).is_dir()
|
||||
&& dunce::canonicalize(name).is_ok()
|
||||
&& which::which(&name).is_err())
|
||||
{
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
path_name.map(|name| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if name.ends_with(':') {
|
||||
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
|
||||
// But first, we need to save where we are now
|
||||
let current_path = ctx.shell_manager.path();
|
||||
|
||||
let split_path: Vec<_> = current_path.split(':').collect();
|
||||
if split_path.len() > 1 {
|
||||
ctx.windows_drives_previous_cwd
|
||||
.lock()
|
||||
.insert(split_path[0].to_string(), current_path);
|
||||
}
|
||||
|
||||
let name = name.to_uppercase();
|
||||
let new_drive: Vec<_> = name.split(':').collect();
|
||||
|
||||
if let Some(val) = ctx.windows_drives_previous_cwd.lock().get(new_drive[0]) {
|
||||
val.to_string()
|
||||
} else {
|
||||
name
|
||||
}
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
name.to_string()
|
||||
}
|
||||
})
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_value_ext::{as_string, get_data_by_column_path};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SelectArgs {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct Select;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Select {
|
||||
fn name(&self) -> &str {
|
||||
"select"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("select").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"the columns to select from the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Down-select table to only these columns."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
select(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Select just the name column",
|
||||
example: "ls | select name",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Select the name and size columns",
|
||||
example: "ls | select name size",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let stream = async_stream! {
|
||||
let (SelectArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Select requires columns to select",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let member = fields.remove(0);
|
||||
let member = vec![member];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<ColumnPath>>();
|
||||
let mut empty = true;
|
||||
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
|
||||
|
||||
while let Some(value) = input.next().await {
|
||||
for path in &column_paths {
|
||||
let path_members_span = span_for_spanned_list(path.members().iter().map(|p| p.span));
|
||||
|
||||
let fetcher = get_data_by_column_path(&value, &path, Box::new(move |(obj_source, path_member_tried, error)| {
|
||||
if let PathMember { unspanned: UnspannedPathMember::String(column), .. } = path_member_tried {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"No data to fetch.",
|
||||
format!("Couldn't select column \"{}\"", column),
|
||||
path_member_tried.span,
|
||||
format!("How about exploring it with \"get\"? Check the input is appropriate originating from here"),
|
||||
obj_source.tag.span)
|
||||
}
|
||||
|
||||
error
|
||||
}));
|
||||
|
||||
|
||||
let field = path.clone();
|
||||
let key = as_string(&UntaggedValue::Primitive(Primitive::ColumnPath(field.clone())).into_untagged_value())?;
|
||||
|
||||
match fetcher {
|
||||
Ok(results) => {
|
||||
match results.value {
|
||||
UntaggedValue::Table(records) => {
|
||||
for x in records {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
out.insert_untagged(&key, x.value.clone());
|
||||
let group = bring_back.entry(key.clone()).or_insert(vec![]);
|
||||
group.push(out.into_value());
|
||||
}
|
||||
},
|
||||
x => {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
out.insert_untagged(&key, x.clone());
|
||||
let group = bring_back.entry(key.clone()).or_insert(vec![]);
|
||||
group.push(out.into_value());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
// At the moment, we can't add switches, named flags
|
||||
// and the like while already using .rest since it
|
||||
// breaks the parser.
|
||||
//
|
||||
// We allow flexibility for now and skip the error
|
||||
// if a given column isn't present.
|
||||
let strict: Option<bool> = None;
|
||||
|
||||
if strict.is_some() {
|
||||
yield Err(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
bring_back.entry(key.clone()).or_insert(vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut max = 0;
|
||||
|
||||
if let Some(max_column) = bring_back.values().max() {
|
||||
max = max_column.len();
|
||||
}
|
||||
|
||||
let keys = bring_back.keys().map(|x| x.clone()).collect::<Vec<String>>();
|
||||
|
||||
for mut current in 0..max {
|
||||
let mut out = TaggedDictBuilder::new(name.clone());
|
||||
|
||||
for k in &keys {
|
||||
let nothing = UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value();
|
||||
let subsets = bring_back.get(k);
|
||||
|
||||
match subsets {
|
||||
Some(set) => {
|
||||
match set.get(current) {
|
||||
Some(row) => out.insert_untagged(k, row.get_data(k).borrow().clone()),
|
||||
None => out.insert_untagged(k, nothing.clone()),
|
||||
}
|
||||
}
|
||||
None => out.insert_untagged(k, nothing.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(out.into_value());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Select;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Select {})
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, Value};
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
|
||||
pub struct Shuffle;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Shuffle {
|
||||
fn name(&self) -> &str {
|
||||
"shuffle"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Shuffle rows randomly."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
shuffle(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn shuffle(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut input = args.input;
|
||||
let mut values: Vec<Value> = input.collect().await;
|
||||
|
||||
let out = {
|
||||
values.shuffle(&mut thread_rng());
|
||||
values.clone()
|
||||
};
|
||||
|
||||
for val in out.into_iter() {
|
||||
yield ReturnSuccess::value(val);
|
||||
}
|
||||
};
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Shuffle;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Shuffle {})
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct SkipUntil;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SkipUntil {
|
||||
fn name(&self) -> &str {
|
||||
"skip-until"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("skip-until")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to stop skipping",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Skips rows until the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let stream = async_stream! {
|
||||
let mut call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut skipping = true;
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
skipping = false;
|
||||
}
|
||||
|
||||
if !skipping {
|
||||
yield ReturnSuccess::value(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SkipUntil;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SkipUntil {})
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct SkipWhile;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SkipWhile {
|
||||
fn name(&self) -> &str {
|
||||
"skip-while"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("skip-while")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to continue skipping",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Skips rows while the condition matches."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let stream = async_stream! {
|
||||
let mut call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut skipping = true;
|
||||
while let Some(item) = call_info.input.next().await {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &item, &scope.vars, &scope.env)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if return_value {
|
||||
skipping = false;
|
||||
}
|
||||
|
||||
if !skipping {
|
||||
yield ReturnSuccess::value(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SkipWhile;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SkipWhile {})
|
||||
}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct SortBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SortByArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SortBy {
|
||||
fn name(&self) -> &str {
|
||||
"sort-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sort-by").rest(SyntaxShape::String, "the column(s) to sort by")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sort by the given columns, in increasing order."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sort_by(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sort list by increasing value",
|
||||
example: "echo [4 2 3 1] | sort-by",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Sort output by increasing file size",
|
||||
example: "ls | sort-by size",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Sort output by type, and then by file size for each type",
|
||||
example: "ls | sort-by type size",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn sort_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let (SortByArgs { rest }, mut input) = args.process(®istry).await?;
|
||||
let mut vec = input.drain_vec().await;
|
||||
|
||||
if vec.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Error performing sort-by command",
|
||||
"sort-by error",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
|
||||
for sort_arg in rest.iter() {
|
||||
let match_test = get_data_by_key(&vec[0], sort_arg.borrow_spanned());
|
||||
if match_test == None {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not find column to sort by",
|
||||
"invalid column",
|
||||
sort_arg.borrow_spanned().span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
match &vec[0] {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(_),
|
||||
..
|
||||
} => {
|
||||
vec.sort();
|
||||
}
|
||||
_ => {
|
||||
let calc_key = |item: &Value| {
|
||||
rest.iter()
|
||||
.map(|f| get_data_by_key(item, f.borrow_spanned()))
|
||||
.collect::<Vec<Option<Value>>>()
|
||||
};
|
||||
vec.sort_by_cached_key(calc_key);
|
||||
}
|
||||
};
|
||||
|
||||
let mut values_vec_deque: VecDeque<Value> = VecDeque::new();
|
||||
|
||||
for item in vec {
|
||||
values_vec_deque.push_back(item);
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SortBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SortBy {})
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Signature, SpannedTypeName, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SplitBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SplitByArgs {
|
||||
column_name: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SplitBy {
|
||||
fn name(&self) -> &str {
|
||||
"split-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("split-by").required(
|
||||
"column_name",
|
||||
SyntaxShape::String,
|
||||
"the name of the column within the nested table to split by",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with the data from the inner tables split by the column given."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
split_by(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn split_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (SplitByArgs { column_name }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.len() > 1 || values.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected table from pipeline",
|
||||
"requires a table input",
|
||||
column_name.span(),
|
||||
));
|
||||
}
|
||||
|
||||
match split(&column_name, &values[0], name) {
|
||||
Ok(split) => Ok(OutputStream::one(split)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(
|
||||
column_name: &Tagged<String>,
|
||||
value: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let origin_tag = tag.into();
|
||||
|
||||
let mut splits = indexmap::IndexMap::new();
|
||||
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Row(group_sets),
|
||||
..
|
||||
} => {
|
||||
for (group_key, group_value) in group_sets.entries.iter() {
|
||||
match *group_value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(ref dataset),
|
||||
..
|
||||
} => {
|
||||
let group = crate::commands::group_by::group(
|
||||
&column_name,
|
||||
dataset.to_vec(),
|
||||
&origin_tag,
|
||||
)?;
|
||||
|
||||
match group {
|
||||
Value {
|
||||
value: UntaggedValue::Row(o),
|
||||
..
|
||||
} => {
|
||||
for (split_label, subset) in o.entries.into_iter() {
|
||||
match subset {
|
||||
Value {
|
||||
value: UntaggedValue::Table(subset),
|
||||
tag,
|
||||
} => {
|
||||
let s = splits
|
||||
.entry(split_label.clone())
|
||||
.or_insert(indexmap::IndexMap::new());
|
||||
s.insert(
|
||||
group_key.clone(),
|
||||
UntaggedValue::table(&subset).into_value(tag),
|
||||
);
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
other.spanned_type_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
group.spanned_type_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
ref other => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
other.spanned_type_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
value.spanned_type_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = TaggedDictBuilder::new(&origin_tag);
|
||||
|
||||
for (k, v) in splits.into_iter() {
|
||||
out.insert_untagged(k, UntaggedValue::row(v));
|
||||
}
|
||||
|
||||
Ok(out.into_value())
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::commands::group_by::group;
|
||||
use crate::commands::split_by::split;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::*;
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
UntaggedValue::row(entries).into_untagged_value()
|
||||
}
|
||||
|
||||
fn table(list: &[Value]) -> Value {
|
||||
UntaggedValue::table(list).into_untagged_value()
|
||||
}
|
||||
|
||||
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
|
||||
let key = String::from("date").tagged_unknown();
|
||||
group(&key, nu_releases_commiters(), Tag::unknown())
|
||||
}
|
||||
|
||||
fn nu_releases_commiters() -> Vec<Value> {
|
||||
vec![
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_inner_tables_by_key() -> Result<(), ShellError> {
|
||||
let for_key = String::from("country").tagged_unknown();
|
||||
|
||||
assert_eq!(
|
||||
split(&for_key, &nu_releases_grouped_by_date()?, Tag::unknown())?,
|
||||
UntaggedValue::row(indexmap! {
|
||||
"EC".into() => row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
])
|
||||
}),
|
||||
"NZ".into() => row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")})
|
||||
])
|
||||
}),
|
||||
"US".into() => row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")})
|
||||
])
|
||||
})
|
||||
}).into_untagged_value()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_if_key_within_some_inner_table_is_missing() {
|
||||
let for_key = String::from("country").tagged_unknown();
|
||||
|
||||
let nu_releases = row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"Sept 24-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => UntaggedValue::string("JT").into_value(Tag::from(Span::new(5,10))), "date".into() => string("Sept 24-2019")})
|
||||
]),
|
||||
"October 10-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")})
|
||||
])
|
||||
});
|
||||
|
||||
assert!(split(&for_key, &nu_releases, Tag::from(Span::new(5, 10))).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::SplitBy;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SplitBy {})
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
mod capitalize;
|
||||
mod command;
|
||||
mod downcase;
|
||||
mod find_replace;
|
||||
mod set;
|
||||
mod substring;
|
||||
mod to_datetime;
|
||||
mod to_decimal;
|
||||
mod to_integer;
|
||||
mod trim;
|
||||
mod upcase;
|
||||
|
||||
pub use capitalize::SubCommand as StrCapitalize;
|
||||
pub use command::Command as Str;
|
||||
pub use downcase::SubCommand as StrDowncase;
|
||||
pub use find_replace::SubCommand as StrFindReplace;
|
||||
pub use set::SubCommand as StrSet;
|
||||
pub use substring::SubCommand as StrSubstring;
|
||||
pub use to_datetime::SubCommand as StrToDatetime;
|
||||
pub use to_decimal::SubCommand as StrToDecimal;
|
||||
pub use to_integer::SubCommand as StrToInteger;
|
||||
pub use trim::SubCommand as StrTrim;
|
||||
pub use upcase::SubCommand as StrUpcase;
|
@ -1,136 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
replace: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str set")
|
||||
.required("set", SyntaxShape::String, "the new string to set")
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally set text by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"sets text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Set contents with preferred string",
|
||||
example: "echo 'good day' | str set 'good bye'",
|
||||
result: Some(vec![Value::from("good bye")]),
|
||||
},
|
||||
Example {
|
||||
description: "Set the contents on preferred column paths",
|
||||
example: "open Cargo.toml | str set '255' package.version",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Replace(String);
|
||||
|
||||
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (Arguments { replace, rest }, mut input) = args.process(®istry).await?;
|
||||
let options = Replace(replace.item);
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
if column_paths.is_empty() {
|
||||
match action(&v, &options, v.tag()) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let mut ret = v.clone();
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
|
||||
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
|
||||
|
||||
match swapping {
|
||||
Ok(new_value) => {
|
||||
ret = new_value;
|
||||
}
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let replacement = &options.0;
|
||||
Ok(UntaggedValue::string(replacement.as_str()).into_value(tag))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, Replace, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets() {
|
||||
let word = string("andres");
|
||||
let expected = string("robalino");
|
||||
|
||||
let set_options = Replace(String::from("robalino"));
|
||||
|
||||
let actual = action(&word, &set_options, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use std::cmp;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
range: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str substring"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str substring")
|
||||
.required(
|
||||
"range",
|
||||
SyntaxShape::String,
|
||||
"the indexes to substring \"start, end\"",
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally substring text by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"substrings text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a substring from the text",
|
||||
example: "echo 'good nushell' | str substring '5,12'",
|
||||
result: Some(vec![Value::from("nushell")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the remaining characters from a starting index",
|
||||
example: "echo 'good nushell' | str substring '5,'",
|
||||
result: Some(vec![Value::from("nushell")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the characters from the beginning until ending index",
|
||||
example: "echo 'good nushell' | str substring ',7'",
|
||||
result: Some(vec![Value::from("good nu")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Substring(usize, usize);
|
||||
|
||||
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (Arguments { range, rest }, mut input) = args.process(®istry).await?;
|
||||
|
||||
let v: Vec<&str> = range.item.split(',').collect();
|
||||
|
||||
let start = match v[0] {
|
||||
"" => 0,
|
||||
_ => v[0]
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"could not perform substring",
|
||||
"could not perform substring",
|
||||
name.span,
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
let end = match v[1] {
|
||||
"" => usize::max_value(),
|
||||
_ => v[1]
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"could not perform substring",
|
||||
"could not perform substring",
|
||||
name.span,
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
if start > end {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"End must be greater than or equal to Start",
|
||||
"End must be greater than or equal to Start",
|
||||
name.span,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let options = Substring(start, end);
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
if column_paths.is_empty() {
|
||||
match action(&v, &options, v.tag()) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let mut ret = v.clone();
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
|
||||
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
|
||||
|
||||
match swapping {
|
||||
Ok(new_value) => {
|
||||
ret = new_value;
|
||||
}
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let start = options.0;
|
||||
let end: usize = cmp::min(options.1, s.len());
|
||||
|
||||
let out = {
|
||||
if start > s.len() - 1 {
|
||||
UntaggedValue::string("")
|
||||
} else {
|
||||
UntaggedValue::string(
|
||||
s.chars().skip(start).take(end - start).collect::<String>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, SubCommand, Substring};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_start_and_end_indexes() {
|
||||
let word = string("andresS");
|
||||
let expected = string("andres");
|
||||
|
||||
let substring_options = Substring(0, 6);
|
||||
|
||||
let actual = action(&word, &substring_options, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, ShellTypeName, Signature, SyntaxShape, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use chrono::DateTime;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
format: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str to-datetime"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str to-datetime")
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into datetime by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts text into datetime"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert to datetime",
|
||||
example: "echo '16.11.1984 8:00 am +0000' | str to-datetime",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DatetimeFormat(String);
|
||||
|
||||
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (Arguments { format, rest }, mut input) = args.process(®istry).await?;
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
|
||||
let options = if let Some(Tagged { item: fmt, tag }) = format {
|
||||
DatetimeFormat(fmt)
|
||||
} else {
|
||||
DatetimeFormat(String::from("%d.%m.%Y %H:%M %P %z"))
|
||||
};
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
if column_paths.is_empty() {
|
||||
match action(&v, &options, v.tag()) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let mut ret = v.clone();
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, &options, old.tag())));
|
||||
|
||||
match swapping {
|
||||
Ok(new_value) => {
|
||||
ret = new_value;
|
||||
}
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
options: &DatetimeFormat,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let dt = &options.0;
|
||||
|
||||
let out = match DateTime::parse_from_str(s, dt) {
|
||||
Ok(d) => UntaggedValue::date(d),
|
||||
Err(_) => UntaggedValue::string(s),
|
||||
};
|
||||
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, DatetimeFormat, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_a_date_format() {
|
||||
let date_str = string("16.11.1984 8:00 am +0000");
|
||||
|
||||
let fmt_options = DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string());
|
||||
|
||||
let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap();
|
||||
|
||||
match actual.value {
|
||||
UntaggedValue::Primitive(Primitive::Date(_)) => {}
|
||||
_ => panic!("Didn't convert to date"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str to-int"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str to-int").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into integer by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts text into integer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert to an integer",
|
||||
example: "echo '255' | str to-int",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (Arguments { rest }, mut input) = args.process(®istry).await?;
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
if column_paths.is_empty() {
|
||||
match action(&v, v.tag()) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let mut ret = v.clone();
|
||||
|
||||
for path in &column_paths {
|
||||
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
|
||||
|
||||
match swapping {
|
||||
Ok(new_value) => {
|
||||
ret = new_value;
|
||||
}
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let other = s.trim();
|
||||
let out = match BigInt::from_str(other) {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
Err(_) => UntaggedValue::string(s),
|
||||
};
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::{int, string};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_to_integer() {
|
||||
let word = string("10");
|
||||
let expected = int(10);
|
||||
|
||||
let actual = action(&word, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str trim"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str trim").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally trim text by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"trims text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Trim contents",
|
||||
example: "echo 'Nu shell ' | str trim",
|
||||
result: Some(vec![Value::from("Nu shell")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let (Arguments { rest }, mut input) = args.process(®istry).await?;
|
||||
|
||||
let column_paths: Vec<_> = rest.iter().map(|x| x.clone()).collect();
|
||||
|
||||
while let Some(v) = input.next().await {
|
||||
if column_paths.is_empty() {
|
||||
match action(&v, v.tag()) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let mut ret = v.clone();
|
||||
|
||||
for path in &column_paths {
|
||||
let swapping = ret.swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())));
|
||||
|
||||
match swapping {
|
||||
Ok(new_value) => {
|
||||
ret = new_value;
|
||||
}
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield ReturnSuccess::value(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
Ok(UntaggedValue::string(s.trim()).into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims() {
|
||||
let word = string("andres ");
|
||||
let expected = string("andres");
|
||||
|
||||
let actual = action(&word, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use num_traits::identities::Zero;
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
pub struct Sum;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Sum {
|
||||
fn name(&self) -> &str {
|
||||
"sum"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sum")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sums the values."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sum(RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
})
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sum a list of numbers",
|
||||
example: "echo [1 2 3] | sum",
|
||||
result: Some(vec![UntaggedValue::int(6).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the disk usage for the current directory",
|
||||
example: "ls --all --du | get size | sum",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn sum(RunnableContext { mut input, .. }: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut values: Vec<Value> = input.drain_vec().await;
|
||||
let action = reducer_for(Reduce::Sum);
|
||||
|
||||
if values.iter().all(|v| if let UntaggedValue::Primitive(_) = v.value {true} else {false}) {
|
||||
let total = action(Value::zero(), values)?;
|
||||
yield ReturnSuccess::value(total)
|
||||
} else {
|
||||
let mut column_values = IndexMap::new();
|
||||
for value in values {
|
||||
match value.value {
|
||||
UntaggedValue::Row(row_dict) => {
|
||||
for (key, value) in row_dict.entries.iter() {
|
||||
column_values
|
||||
.entry(key.clone())
|
||||
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
|
||||
.or_insert(vec![value.clone()]);
|
||||
}
|
||||
},
|
||||
table => {},
|
||||
};
|
||||
}
|
||||
|
||||
let mut column_totals = IndexMap::new();
|
||||
for (col_name, col_vals) in column_values {
|
||||
let sum = action(Value::zero(), col_vals);
|
||||
match sum {
|
||||
Ok(value) => {
|
||||
column_totals.insert(col_name, value);
|
||||
},
|
||||
Err(err) => yield Err(err),
|
||||
};
|
||||
}
|
||||
yield ReturnSuccess::value(
|
||||
UntaggedValue::Row(Dictionary {entries: column_totals}).into_untagged_value())
|
||||
}
|
||||
};
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Sum;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Sum {})
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::TaggedListBuilder;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{columns_sorted, t_sort};
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct TSortBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TSortByArgs {
|
||||
#[serde(rename(deserialize = "show-columns"))]
|
||||
show_columns: bool,
|
||||
group_by: Option<Tagged<String>>,
|
||||
#[allow(unused)]
|
||||
split_by: Option<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for TSortBy {
|
||||
fn name(&self) -> &str {
|
||||
"t-sort-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("t-sort-by")
|
||||
.switch(
|
||||
"show-columns",
|
||||
"Displays the column names sorted",
|
||||
Some('c'),
|
||||
)
|
||||
.named(
|
||||
"group_by",
|
||||
SyntaxShape::String,
|
||||
"the name of the column to group by",
|
||||
Some('g'),
|
||||
)
|
||||
.named(
|
||||
"split_by",
|
||||
SyntaxShape::String,
|
||||
"the name of the column within the grouped by table to split by",
|
||||
Some('s'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sort by the given columns."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
t_sort_by(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn t_sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (TSortByArgs { show_columns, group_by, ..}, mut input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let column_grouped_by_name = if let Some(grouped_by) = group_by {
|
||||
Some(grouped_by.item().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if show_columns {
|
||||
for label in columns_sorted(column_grouped_by_name, &values[0], &name).into_iter() {
|
||||
yield ReturnSuccess::value(UntaggedValue::string(label.item).into_value(label.tag));
|
||||
}
|
||||
} else {
|
||||
match t_sort(column_grouped_by_name, None, &values[0], name) {
|
||||
Ok(sorted) => yield ReturnSuccess::value(sorted),
|
||||
Err(err) => yield Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TSortBy;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(TSortBy {})
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::format::TableView;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::time::Instant;
|
||||
|
||||
const STREAM_PAGE_SIZE: usize = 1000;
|
||||
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
|
||||
|
||||
pub struct Table;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Table {
|
||||
fn name(&self) -> &str {
|
||||
"table"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("table").named(
|
||||
"start_number",
|
||||
SyntaxShape::Number,
|
||||
"row number to start viewing from",
|
||||
Some('n'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View the contents of the pipeline as a table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
table(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let mut args = args.evaluate_once(®istry).await?;
|
||||
let mut finished = false;
|
||||
|
||||
let host = args.host.clone();
|
||||
let mut start_number = match args.get("start_number") {
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::Int(i)), .. }) => {
|
||||
if let Some(num) = i.to_usize() {
|
||||
num
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Expected a row number", "expected a row number", &args.args.call_info.name_tag));
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let mut delay_slot = None;
|
||||
|
||||
while !finished {
|
||||
let mut new_input: VecDeque<Value> = VecDeque::new();
|
||||
|
||||
let start_time = Instant::now();
|
||||
for idx in 0..STREAM_PAGE_SIZE {
|
||||
if let Some(val) = delay_slot {
|
||||
new_input.push_back(val);
|
||||
delay_slot = None;
|
||||
} else {
|
||||
match args.input.next().await {
|
||||
Some(a) => {
|
||||
if !new_input.is_empty() {
|
||||
if let Some(descs) = new_input.get(0) {
|
||||
let descs = descs.data_descriptors();
|
||||
let compare = a.data_descriptors();
|
||||
if descs != compare {
|
||||
delay_slot = Some(a);
|
||||
break;
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we've gone over our buffering threshold
|
||||
if (idx + 1) % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
|
||||
let end_time = Instant::now();
|
||||
|
||||
// If we've been buffering over a second, go ahead and send out what we have so far
|
||||
if (end_time - start_time).as_secs() >= 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input: Vec<Value> = new_input.into();
|
||||
|
||||
if input.len() > 0 {
|
||||
let mut host = host.lock();
|
||||
let view = TableView::from_list(&input, start_number);
|
||||
|
||||
if let Some(view) = view {
|
||||
handle_unexpected(&mut *host, |host| crate::format::print_view(&view, host));
|
||||
}
|
||||
}
|
||||
|
||||
start_number += input.len();
|
||||
}
|
||||
|
||||
// Needed for async_stream to type check
|
||||
if false {
|
||||
yield ReturnSuccess::value(UntaggedValue::nothing().into_value(Tag::unknown()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::new(stream))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Table;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Table {})
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
use crate::commands::to_delimited_data::to_delimited_data;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct ToCSV;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ToCSVArgs {
|
||||
headerless: bool,
|
||||
separator: Option<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToCSV {
|
||||
fn name(&self) -> &str {
|
||||
"to csv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to csv")
|
||||
.named(
|
||||
"separator",
|
||||
SyntaxShape::String,
|
||||
"a character to separate columns, defaults to ','",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
"headerless",
|
||||
"do not output the columns names as the first row",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into .csv text "
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_csv(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_csv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (ToCSVArgs { separator, headerless }, mut input) = args.process(®istry).await?;
|
||||
let sep = match separator {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
tag,
|
||||
..
|
||||
}) => {
|
||||
if s == r"\t" {
|
||||
'\t'
|
||||
} else {
|
||||
let vec_s: Vec<char> = s.chars().collect();
|
||||
if vec_s.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a single separator char from --separator",
|
||||
"requires a single character string input",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
};
|
||||
vec_s[0]
|
||||
}
|
||||
}
|
||||
_ => ',',
|
||||
};
|
||||
|
||||
let mut result = to_delimited_data(headerless, sep, "CSV", input, name)?;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
yield item;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToCSV;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToCSV {})
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_source::AnchorLocation;
|
||||
|
||||
pub struct ToHTML;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToHTML {
|
||||
fn name(&self) -> &str {
|
||||
"to html"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to html")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into simple HTML"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_html(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_html(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let name_tag = args.name_tag();
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = "<html><body>".to_string();
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
|
||||
output_string.push_str("<table>");
|
||||
|
||||
output_string.push_str("<tr>");
|
||||
for header in &headers {
|
||||
output_string.push_str("<th>");
|
||||
output_string.push_str(&htmlescape::encode_minimal(&header));
|
||||
output_string.push_str("</th>");
|
||||
}
|
||||
output_string.push_str("</tr>");
|
||||
}
|
||||
|
||||
for row in input {
|
||||
match row.value {
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
// This might be a bit much, but it's fun :)
|
||||
match row.tag.anchor {
|
||||
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
|
||||
let extension = f.split('.').last().map(String::from);
|
||||
match extension {
|
||||
Some(s)
|
||||
if ["png", "jpg", "bmp", "gif", "tiff", "jpeg"]
|
||||
.contains(&s.to_lowercase().as_str()) =>
|
||||
{
|
||||
output_string.push_str("<img src=\"data:image/");
|
||||
output_string.push_str(&s);
|
||||
output_string.push_str(";base64,");
|
||||
output_string.push_str(&base64::encode(&b));
|
||||
output_string.push_str("\">");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::String(ref b)) => {
|
||||
// This might be a bit much, but it's fun :)
|
||||
match row.tag.anchor {
|
||||
Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => {
|
||||
let extension = f.split('.').last().map(String::from);
|
||||
match extension {
|
||||
Some(s) if s.to_lowercase() == "svg" => {
|
||||
output_string.push_str("<img src=\"data:image/svg+xml;base64,");
|
||||
output_string.push_str(&base64::encode(&b.as_bytes()));
|
||||
output_string.push_str("\">");
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
output_string.push_str(
|
||||
&(htmlescape::encode_minimal(&format_leaf(&row.value).plain_string(100_000))
|
||||
.replace("\n", "<br>")),
|
||||
);
|
||||
}
|
||||
UntaggedValue::Row(row) => {
|
||||
output_string.push_str("<tr>");
|
||||
for header in &headers {
|
||||
let data = row.get_data(header);
|
||||
output_string.push_str("<td>");
|
||||
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
|
||||
output_string.push_str("</td>");
|
||||
}
|
||||
output_string.push_str("</tr>");
|
||||
}
|
||||
p => {
|
||||
output_string.push_str(
|
||||
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))
|
||||
.replace("\n", "<br>")),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
|
||||
output_string.push_str("</table>");
|
||||
}
|
||||
output_string.push_str("</body></html>");
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToHTML;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToHTML {})
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct ToMarkdown;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToMarkdown {
|
||||
fn name(&self) -> &str {
|
||||
"to md"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to md")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into simple Markdown"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_html(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_html(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let name_tag = args.name_tag();
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = String::new();
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
output_string.push_str(&htmlescape::encode_minimal(&header));
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n|");
|
||||
for _ in &headers {
|
||||
output_string.push_str("-");
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
|
||||
for row in input {
|
||||
match row.value {
|
||||
UntaggedValue::Row(row) => {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
let data = row.get_data(header);
|
||||
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
p => {
|
||||
output_string.push_str(
|
||||
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
|
||||
);
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToMarkdown;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToMarkdown {})
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use crate::commands::to_delimited_data::to_delimited_data;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct ToTSV;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ToTSVArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToTSV {
|
||||
fn name(&self) -> &str {
|
||||
"to tsv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to tsv").switch(
|
||||
"headerless",
|
||||
"do not output the column names as the first row",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into .tsv text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_tsv(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tsv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (ToTSVArgs { headerless }, mut input) = args.process(®istry).await?;
|
||||
let mut result = to_delimited_data(
|
||||
headerless,
|
||||
'\t',
|
||||
"TSV",
|
||||
input,
|
||||
name,
|
||||
)?;
|
||||
|
||||
while let Some(item) = result.next().await {
|
||||
yield item;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToTSV;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToTSV {})
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct ToURL;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToURL {
|
||||
fn name(&self) -> &str {
|
||||
"to url"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to url")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into url-encoded text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_url(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
for value in input {
|
||||
match value {
|
||||
Value { value: UntaggedValue::Row(row), .. } => {
|
||||
let mut row_vec = vec![];
|
||||
for (k,v) in row.entries {
|
||||
match v.as_string() {
|
||||
Ok(s) => {
|
||||
row_vec.push((k.clone(), s.to_string()));
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected table with string values",
|
||||
"requires table with strings",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
v.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match serde_urlencoded::to_string(row_vec) {
|
||||
Ok(s) => {
|
||||
yield ReturnSuccess::value(UntaggedValue::string(s).into_value(&tag));
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Failed to convert to url-encoded",
|
||||
"cannot url-encode",
|
||||
&tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag: value_tag, .. } => {
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a table from pipeline",
|
||||
"requires table input",
|
||||
&tag,
|
||||
"value originates from here",
|
||||
value_tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToURL;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToURL {})
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Touch;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TouchArgs {
|
||||
pub target: Tagged<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Touch {
|
||||
fn name(&self) -> &str {
|
||||
"touch"
|
||||
}
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("touch").required(
|
||||
"filename",
|
||||
SyntaxShape::Path,
|
||||
"the path of the file you want to create",
|
||||
)
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
"creates a file"
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
touch(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates \"fixture.json\"",
|
||||
example: "touch fixture.json",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn touch(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let stream = async_stream! {
|
||||
let (TouchArgs { target }, _) = args.process(®istry).await?;
|
||||
match OpenOptions::new().write(true).create(true).open(&target) {
|
||||
Ok(_) => {},
|
||||
Err(err) => yield Err(ShellError::labeled_error(
|
||||
"File Error",
|
||||
err.to_string(),
|
||||
&target.tag,
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Touch;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Touch {})
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Trim;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Trim {
|
||||
fn name(&self) -> &str {
|
||||
"trim"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("trim")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Trim leading and following whitespace from text data."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
trim(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Trims surrounding whitespace and outputs \"Hello world\"",
|
||||
example: "echo \" Hello world\" | trim",
|
||||
result: Some(vec![Value::from("Hello world")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_primitive(p: &mut Primitive) {
|
||||
match p {
|
||||
Primitive::String(s) | Primitive::Line(s) => *p = Primitive::String(s.trim().to_string()),
|
||||
Primitive::Nothing
|
||||
| Primitive::Int(_)
|
||||
| Primitive::Decimal(_)
|
||||
| Primitive::Bytes(_)
|
||||
| Primitive::ColumnPath(_)
|
||||
| Primitive::Pattern(_)
|
||||
| Primitive::Boolean(_)
|
||||
| Primitive::Date(_)
|
||||
| Primitive::Duration(_)
|
||||
| Primitive::Range(_)
|
||||
| Primitive::Path(_)
|
||||
| Primitive::Binary(_)
|
||||
| Primitive::BeginningOfStream
|
||||
| Primitive::EndOfStream => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_row(d: &mut Dictionary) {
|
||||
for (_, mut value) in d.entries.iter_mut() {
|
||||
trim_value(&mut value);
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_value(v: &mut Value) {
|
||||
match &mut v.value {
|
||||
UntaggedValue::Primitive(p) => trim_primitive(p),
|
||||
UntaggedValue::Row(row) => trim_row(row),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(|v| {
|
||||
let mut trimmed = v;
|
||||
trim_value(&mut trimmed);
|
||||
ReturnSuccess::value(trimmed)
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Trim;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Trim {})
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use indexmap::set::IndexSet;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature};
|
||||
|
||||
pub struct Uniq;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Uniq {
|
||||
fn name(&self) -> &str {
|
||||
"uniq"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("uniq")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return the unique rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
uniq(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn uniq(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
let uniq_values: IndexSet<_> = input.collect().await;
|
||||
|
||||
let mut values_vec_deque = VecDeque::new();
|
||||
|
||||
for item in uniq_values
|
||||
.iter()
|
||||
.map(|row| ReturnSuccess::value(row.clone()))
|
||||
{
|
||||
values_vec_deque.push_back(item);
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Uniq;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Uniq {})
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Update;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateArgs {
|
||||
field: ColumnPath,
|
||||
replacement: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Update {
|
||||
fn name(&self) -> &str {
|
||||
"update"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("update")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"replacement value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Update an existing column to have a new value."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
update(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn update(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let (UpdateArgs { field, replacement }, mut input) = raw_args.process(®istry).await?;
|
||||
while let Some(input) = input.next().await {
|
||||
let replacement = replacement.clone();
|
||||
match replacement {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
yield Err(error.clone());
|
||||
}
|
||||
|
||||
match input {
|
||||
obj @ Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if let Some(result) = stream.next().await {
|
||||
match obj.replace_data_at_column_path(&field, result.clone()) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match input {
|
||||
obj @ Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.replace_data_at_column_path(&field, replacement.clone()) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Update;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Update {})
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, UntaggedValue};
|
||||
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Version {
|
||||
fn name(&self) -> &str {
|
||||
"version"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("version")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display Nu version"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
version(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Display Nu version",
|
||||
example: "version",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.args.span;
|
||||
|
||||
let mut indexmap = IndexMap::new();
|
||||
indexmap.insert(
|
||||
"version".to_string(),
|
||||
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
|
||||
);
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Version;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Version {})
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue};
|
||||
|
||||
pub struct What;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WhatArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for What {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("describe")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Describes the objects in the stream."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
// args.process(registry, what)?.run()
|
||||
what(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn what(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut input = args.input;
|
||||
while let Some(row) = input.next().await {
|
||||
let name = value::format_type(&row, 100);
|
||||
yield ReturnSuccess::value(UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)));
|
||||
}
|
||||
};
|
||||
|
||||
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
|
||||
|
||||
Ok(OutputStream::from(stream))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::What;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(What {})
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, hir::ClassifiedCommand, ReturnSuccess, Signature, SyntaxShape};
|
||||
|
||||
pub struct Where;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WhereArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Where {
|
||||
fn name(&self) -> &str {
|
||||
"where"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("where").required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must match",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Filter table to match the condition."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
where_command(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "List all files in the current directory with sizes greater than 2kb",
|
||||
example: "ls | where size > 2kb",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List only the files in the current directory",
|
||||
example: "ls | where type == File",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all files with names that contain \"Car\"",
|
||||
example: "ls | where name =~ \"Car\"",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all files that were modified in the last two months",
|
||||
example: "ls | where modified <= 2M",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
fn where_command(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let stream = async_stream! {
|
||||
let (WhereArgs { block }, mut input) = raw_args.process(®istry).await?;
|
||||
let condition = {
|
||||
if block.block.len() != 1 {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut input = input;
|
||||
while let Some(input) = input.next().await {
|
||||
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition = evaluate_baseline_expr(&condition, ®istry, &input, &scope.vars, &scope.env).await?;
|
||||
|
||||
match condition.as_bool() {
|
||||
Ok(b) => {
|
||||
if b {
|
||||
yield Ok(ReturnSuccess::Value(input));
|
||||
}
|
||||
}
|
||||
Err(e) => yield Err(e),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Where;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Where {})
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Which;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Which {
|
||||
fn name(&self) -> &str {
|
||||
"which"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("which")
|
||||
.required("application", SyntaxShape::String, "application")
|
||||
.switch("all", "list all executables", Some('a'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds a program file."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
which(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcuts for creating an entry to the output table
|
||||
fn entry(arg: impl Into<String>, path: Value, builtin: bool, tag: Tag) -> Value {
|
||||
let mut map = IndexMap::new();
|
||||
map.insert(
|
||||
"arg".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(arg.into())).into_value(tag.clone()),
|
||||
);
|
||||
map.insert("path".to_string(), path);
|
||||
map.insert(
|
||||
"builtin".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::Boolean(builtin)).into_value(tag.clone()),
|
||||
);
|
||||
|
||||
UntaggedValue::row(map).into_value(tag)
|
||||
}
|
||||
|
||||
macro_rules! entry_builtin {
|
||||
($arg:expr, $tag:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::String("nushell built-in command".to_string()))
|
||||
.into_value($tag.clone()),
|
||||
true,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! entry_path {
|
||||
($arg:expr, $path:expr, $tag:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::Path($path)).into_value($tag.clone()),
|
||||
false,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WhichArgs {
|
||||
application: Tagged<String>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
fn which(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let mut all = true;
|
||||
let stream = async_stream! {
|
||||
let (WhichArgs { application, all: all_items }, _) = args.process(®istry).await?;
|
||||
all = all_items;
|
||||
let external = application.starts_with('^');
|
||||
let item = if external {
|
||||
application.item[1..].to_string()
|
||||
} else {
|
||||
application.item.clone()
|
||||
};
|
||||
if !external {
|
||||
let builtin = registry.has(&item);
|
||||
if builtin {
|
||||
yield ReturnSuccess::value(entry_builtin!(item, application.tag.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(paths) = ichwh::which_all(&item).await {
|
||||
for path in paths {
|
||||
yield ReturnSuccess::value(entry_path!(item, path.into(), application.tag.clone()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if all {
|
||||
Ok(stream.to_output_stream())
|
||||
} else {
|
||||
Ok(stream.take(1).to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Which;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Which {})
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct WithEnv;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WithEnvArgs {
|
||||
variable: (Tagged<String>, Tagged<String>),
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for WithEnv {
|
||||
fn name(&self) -> &str {
|
||||
"with-env"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("with-env")
|
||||
.required(
|
||||
"variable",
|
||||
SyntaxShape::Any,
|
||||
"the environment variable to temporarily set",
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run once the variable is set",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block with an environment set. Eg) with-env [NAME 'foo'] { echo $nu.env.NAME }"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
with_env(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set the MYENV environment variable",
|
||||
example: r#"with-env [MYENV "my env value"] { echo $nu.env.MYENV }"#,
|
||||
result: Some(vec![Value::from("my env value")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_env(raw_args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let mut scope = raw_args
|
||||
.call_info
|
||||
.scope
|
||||
.clone();
|
||||
let (WithEnvArgs { variable, block }, mut input) = raw_args.process(®istry).await?;
|
||||
|
||||
scope.env.insert(variable.0.item, variable.1.item);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(result) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(result));
|
||||
}
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
yield Err(error.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::WithEnv;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(WithEnv {})
|
||||
}
|
||||
}
|
141
crates/nu-cli/src/completion/command.rs
Normal file
141
crates/nu-cli/src/completion/command.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use std::iter::FromIterator;
|
||||
use std::path::Path;
|
||||
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
pub struct CommandCompleter;
|
||||
|
||||
impl Completer for CommandCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(context.scope.get_command_names());
|
||||
|
||||
// Command suggestions can come from three possible sets:
|
||||
// 1. internal command names,
|
||||
// 2. external command names relative to PATH env var, and
|
||||
// 3. any other executable (that matches what's been typed so far).
|
||||
|
||||
let path_executables = find_path_executables().unwrap_or_default();
|
||||
|
||||
// TODO quote these, if necessary
|
||||
commands.extend(path_executables.into_iter());
|
||||
|
||||
let mut suggestions: Vec<_> = commands
|
||||
.into_iter()
|
||||
.filter(|v| matcher.matches(partial, v))
|
||||
.map(|v| Suggestion {
|
||||
replacement: v.clone(),
|
||||
display: v,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !partial.is_empty() {
|
||||
let path_completer = crate::completion::path::PathCompleter;
|
||||
let path_results = path_completer.path_suggestions(partial, matcher);
|
||||
let iter = path_results.into_iter().filter_map(|path_suggestion| {
|
||||
let path = path_suggestion.path;
|
||||
if path.is_dir() || is_executable(&path) {
|
||||
Some(path_suggestion.suggestion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
suggestions.extend(iter);
|
||||
}
|
||||
|
||||
suggestions
|
||||
}
|
||||
}
|
||||
|
||||
// TODO create a struct for "is executable" and store this information in it so we don't recompute
|
||||
// on every dir entry
|
||||
|
||||
#[cfg(windows)]
|
||||
fn pathext() -> Option<Vec<String>> {
|
||||
std::env::var_os("PATHEXT").map(|v| {
|
||||
v.to_string_lossy()
|
||||
.split(';')
|
||||
// Filter out empty tokens and ';' at the end
|
||||
.filter(|f| f.len() > 1)
|
||||
// Cut off the leading '.' character
|
||||
.map(|ext| ext[1..].to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
// If the entry isn't a file, it cannot be executable
|
||||
if !(file_type.is_file() || file_type.is_symlink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(extension) = path.extension() {
|
||||
if let Some(exts) = pathext() {
|
||||
exts.iter()
|
||||
.any(|ext| extension.to_string_lossy().eq_ignore_ascii_case(ext))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn is_executable(_path: &Path) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
let filetype = metadata.file_type();
|
||||
let permissions = metadata.permissions();
|
||||
|
||||
// The file is executable if it is a directory or a symlink and the permissions are set for
|
||||
// owner, group, or other
|
||||
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO cache these, but watch for changes to PATH
|
||||
fn find_path_executables() -> Option<IndexSet<String>> {
|
||||
let path_var = std::env::var_os("PATH")?;
|
||||
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
|
||||
|
||||
let mut executables: IndexSet<String> = IndexSet::new();
|
||||
for path in paths {
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if is_executable(&item.path()) {
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(executables)
|
||||
}
|
434
crates/nu-cli/src/completion/engine.rs
Normal file
434
crates/nu-cli/src/completion/engine.rs
Normal file
@ -0,0 +1,434 @@
|
||||
use nu_protocol::hir::*;
|
||||
use nu_source::{Span, Spanned, SpannedItem};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum LocationType {
|
||||
Command,
|
||||
Flag(String), // command name
|
||||
Argument(Option<String>, Option<String>), // command name, argument name
|
||||
Variable,
|
||||
}
|
||||
|
||||
pub type CompletionLocation = Spanned<LocationType>;
|
||||
|
||||
// TODO The below is very similar to shapes / expression_to_flat_shape. Check back October 2020
|
||||
// to see if we're close enough to just make use of those.
|
||||
|
||||
struct Flatten<'s> {
|
||||
line: &'s str,
|
||||
command: Option<String>,
|
||||
flag: Option<String>,
|
||||
}
|
||||
|
||||
impl<'s> Flatten<'s> {
|
||||
/// Converts a SpannedExpression into a completion location for use in NuCompleter
|
||||
fn expression(&self, e: &SpannedExpression) -> Vec<CompletionLocation> {
|
||||
match &e.expr {
|
||||
Expression::Block(block) => self.completion_locations(block),
|
||||
Expression::Invocation(block) => self.completion_locations(block),
|
||||
Expression::List(exprs) => exprs.iter().flat_map(|v| self.expression(v)).collect(),
|
||||
Expression::Table(headers, cells) => headers
|
||||
.iter()
|
||||
.flat_map(|v| self.expression(v))
|
||||
.chain(
|
||||
cells
|
||||
.iter()
|
||||
.flat_map(|v| v.iter().flat_map(|v| self.expression(v))),
|
||||
)
|
||||
.collect(),
|
||||
Expression::Command => vec![LocationType::Command.spanned(e.span)],
|
||||
Expression::Path(path) => self.expression(&path.head),
|
||||
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
|
||||
|
||||
Expression::Boolean(_)
|
||||
| Expression::FilePath(_)
|
||||
| Expression::Literal(Literal::ColumnPath(_))
|
||||
| Expression::Literal(Literal::GlobPattern(_))
|
||||
| Expression::Literal(Literal::Number(_))
|
||||
| Expression::Literal(Literal::Size(_, _))
|
||||
| Expression::Literal(Literal::String(_)) => {
|
||||
vec![
|
||||
LocationType::Argument(self.command.clone(), self.flag.clone()).spanned(e.span),
|
||||
]
|
||||
}
|
||||
|
||||
Expression::Binary(binary) => {
|
||||
let mut result = Vec::new();
|
||||
result.append(&mut self.expression(&binary.left));
|
||||
result.append(&mut self.expression(&binary.right));
|
||||
result
|
||||
}
|
||||
Expression::Range(range) => {
|
||||
let mut result = Vec::new();
|
||||
if let Some(left) = &range.left {
|
||||
result.append(&mut self.expression(left));
|
||||
}
|
||||
if let Some(right) = &range.right {
|
||||
result.append(&mut self.expression(right));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
Expression::ExternalWord
|
||||
| Expression::ExternalCommand(_)
|
||||
| Expression::Synthetic(_)
|
||||
| Expression::Literal(Literal::Operator(_))
|
||||
| Expression::Literal(Literal::Bare(_))
|
||||
| Expression::Garbage => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_command(&self, internal: &InternalCommand) -> Vec<CompletionLocation> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
match internal.args.head.expr {
|
||||
Expression::Command => {
|
||||
result.push(LocationType::Command.spanned(internal.name_span));
|
||||
}
|
||||
Expression::Literal(Literal::String(_)) => {
|
||||
result.push(LocationType::Command.spanned(internal.name_span));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(positionals) = &internal.args.positional {
|
||||
let mut positionals = positionals.iter();
|
||||
|
||||
if internal.name == "run_external" {
|
||||
if let Some(external_command) = positionals.next() {
|
||||
result.push(LocationType::Command.spanned(external_command.span));
|
||||
}
|
||||
}
|
||||
|
||||
result.extend(positionals.flat_map(|positional| match positional.expr {
|
||||
Expression::Garbage => {
|
||||
let garbage = positional.span.slice(self.line);
|
||||
let location = if garbage.starts_with('-') {
|
||||
LocationType::Flag(internal.name.clone())
|
||||
} else {
|
||||
// TODO we may be able to map this to the name of a positional,
|
||||
// but we'll need a signature
|
||||
LocationType::Argument(Some(internal.name.clone()), None)
|
||||
};
|
||||
|
||||
vec![location.spanned(positional.span)]
|
||||
}
|
||||
|
||||
_ => self.expression(positional),
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, kind) in &named.named {
|
||||
match kind {
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
|
||||
}
|
||||
|
||||
NamedValue::Value(span, expr) => {
|
||||
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
|
||||
result.append(&mut self.with_flag(name.clone()).expression(expr));
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn pipeline(&self, pipeline: &Pipeline) -> Vec<CompletionLocation> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for command in &pipeline.list {
|
||||
match command {
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
let engine = self.with_command(internal.name.clone());
|
||||
result.append(&mut engine.internal_command(internal));
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => result.append(&mut self.expression(expr)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Flattens the block into a Vec of completion locations
|
||||
pub fn completion_locations(&self, block: &Block) -> Vec<CompletionLocation> {
|
||||
block
|
||||
.block
|
||||
.iter()
|
||||
.flat_map(|g| g.pipelines.iter().flat_map(|v| self.pipeline(v)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new(line: &'s str) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line,
|
||||
command: None,
|
||||
flag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_command(&self, command: String) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line: self.line,
|
||||
command: Some(command),
|
||||
flag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_flag(&self, flag: String) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line: self.line,
|
||||
command: self.command.clone(),
|
||||
flag: Some(flag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Characters that precede a command name
|
||||
const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';'];
|
||||
|
||||
/// Determines the completion location for a given block at the given cursor position
|
||||
pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec<CompletionLocation> {
|
||||
let completion_engine = Flatten::new(line);
|
||||
let locations = completion_engine.completion_locations(block);
|
||||
|
||||
if locations.is_empty() {
|
||||
vec![LocationType::Command.spanned(Span::unknown())]
|
||||
} else {
|
||||
let mut command = None;
|
||||
let mut prev = None;
|
||||
for loc in locations {
|
||||
// We don't use span.contains because we want to include the end. This handles the case
|
||||
// where the cursor is just after the text (i.e., no space between cursor and text)
|
||||
if loc.span.start() <= pos && pos <= loc.span.end() {
|
||||
// The parser sees the "-" in `cmd -` as an argument, but the user is likely
|
||||
// expecting a flag.
|
||||
return match loc.item {
|
||||
LocationType::Argument(ref cmd, _) => {
|
||||
if loc.span.slice(line) == "-" {
|
||||
let cmd = cmd.clone();
|
||||
let span = loc.span;
|
||||
vec![
|
||||
loc,
|
||||
LocationType::Flag(cmd.unwrap_or_default()).spanned(span),
|
||||
]
|
||||
} else {
|
||||
vec![loc]
|
||||
}
|
||||
}
|
||||
_ => vec![loc],
|
||||
};
|
||||
} else if pos < loc.span.start() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let LocationType::Command = loc.item {
|
||||
command = Some(String::from(loc.span.slice(line)));
|
||||
}
|
||||
|
||||
prev = Some(loc);
|
||||
}
|
||||
|
||||
if let Some(prev) = prev {
|
||||
// Cursor is between locations (or at the end). Look at the line to see if the cursor
|
||||
// is after some character that would imply we're in the command position.
|
||||
let start = prev.span.end();
|
||||
if line[start..pos].contains(BEFORE_COMMAND_CHARS) {
|
||||
vec![LocationType::Command.spanned(Span::new(pos, pos))]
|
||||
} else {
|
||||
// TODO this should be able to be mapped to a command
|
||||
vec![LocationType::Argument(command, None).spanned(Span::new(pos, pos))]
|
||||
}
|
||||
} else {
|
||||
// Cursor is before any possible completion location, so must be a command
|
||||
vec![LocationType::Command.spanned(Span::unknown())]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::{classify_block, lex, parse_block, ParserScope};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VecRegistry(Vec<Signature>);
|
||||
|
||||
impl From<Vec<Signature>> for VecRegistry {
|
||||
fn from(v: Vec<Signature>) -> Self {
|
||||
VecRegistry(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParserScope for VecRegistry {
|
||||
fn has_signature(&self, name: &str) -> bool {
|
||||
self.0.iter().any(|v| v.name == name)
|
||||
}
|
||||
|
||||
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> {
|
||||
self.0.iter().find(|v| v.name == name).map(Clone::clone)
|
||||
}
|
||||
|
||||
fn get_alias(&self, _name: &str) -> Option<Vec<Spanned<String>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_alias(&self, _name: &str, _replacement: Vec<Spanned<String>>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn add_definition(&self, _block: Block) {}
|
||||
|
||||
fn get_definitions(&self) -> Vec<Block> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn enter_scope(&self) {}
|
||||
|
||||
fn exit_scope(&self) {}
|
||||
}
|
||||
|
||||
mod completion_location {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::ParserScope;
|
||||
|
||||
fn completion_location(
|
||||
line: &str,
|
||||
scope: &dyn ParserScope,
|
||||
pos: usize,
|
||||
) -> Vec<LocationType> {
|
||||
let (tokens, _) = lex(line, 0);
|
||||
let (lite_block, _) = parse_block(tokens);
|
||||
|
||||
scope.enter_scope();
|
||||
let (block, _) = classify_block(&lite_block, scope);
|
||||
scope.exit_scope();
|
||||
|
||||
super::completion_location(line, &block, pos)
|
||||
.into_iter()
|
||||
.map(|v| v.item)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_internal_command_names() {
|
||||
let registry: VecRegistry =
|
||||
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 10),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_external_command_names() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 10),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_command_names_when_cursor_immediately_after_command_name() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 4),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_variables() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo $nu.env.";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 13),
|
||||
vec![LocationType::Variable],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_flags() {
|
||||
let registry: VecRegistry = vec![Signature::build("du")
|
||||
.switch("recursive", "the values to echo", None)
|
||||
.rest(SyntaxShape::Any, "blah")]
|
||||
.into();
|
||||
|
||||
let line = "du --recurs";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 7),
|
||||
vec![LocationType::Flag("du".to_string())],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_incomplete_nested_structure() {
|
||||
let registry: VecRegistry = vec![Signature::build("sys")].into();
|
||||
let line = "echo $(sy";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 8),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_command_name_for_argument() {
|
||||
let registry: VecRegistry = vec![Signature::build("cd")].into();
|
||||
let line = "cd ";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 3),
|
||||
vec![LocationType::Argument(Some("cd".to_string()), None)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_flags_with_just_a_single_hyphen() {
|
||||
let registry: VecRegistry = vec![Signature::build("du")
|
||||
.switch("recursive", "the values to echo", None)
|
||||
.rest(SyntaxShape::Any, "blah")]
|
||||
.into();
|
||||
|
||||
let line = "du -";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 3),
|
||||
vec![
|
||||
LocationType::Argument(Some("du".to_string()), None),
|
||||
LocationType::Flag("du".to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_arguments() {
|
||||
let registry: VecRegistry =
|
||||
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 6),
|
||||
vec![LocationType::Argument(Some("echo".to_string()), None)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
41
crates/nu-cli/src/completion/flag.rs
Normal file
41
crates/nu-cli/src/completion/flag.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
pub struct FlagCompleter {
|
||||
pub(crate) cmd: String,
|
||||
}
|
||||
|
||||
impl Completer for FlagCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
|
||||
if let Some(cmd) = context.scope.get_command(&self.cmd) {
|
||||
let sig = cmd.signature();
|
||||
let mut suggestions = Vec::new();
|
||||
for (name, (named_type, _desc)) in sig.named.iter() {
|
||||
suggestions.push(format!("--{}", name));
|
||||
|
||||
if let Some(c) = named_type.get_short() {
|
||||
suggestions.push(format!("-{}", c));
|
||||
}
|
||||
}
|
||||
|
||||
suggestions
|
||||
.into_iter()
|
||||
.filter(|v| matcher.matches(partial, v))
|
||||
.map(|v| Suggestion {
|
||||
replacement: format!("{} ", v),
|
||||
display: v,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
45
crates/nu-cli/src/completion/matchers/case_insensitive.rs
Normal file
45
crates/nu-cli/src/completion/matchers/case_insensitive.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::completion::matchers;
|
||||
pub struct Matcher;
|
||||
|
||||
impl matchers::Matcher for Matcher {
|
||||
fn matches(&self, partial: &str, from: &str) -> bool {
|
||||
from.to_ascii_lowercase()
|
||||
.starts_with(partial.to_ascii_lowercase().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: check some unicode matches if this becomes relevant
|
||||
|
||||
// FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test
|
||||
#[test]
|
||||
fn completes_exact_matches() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(matcher.matches("shouldmatch", "shouldmatch"));
|
||||
assert!(matcher.matches("shouldm", "shouldmatch"));
|
||||
assert!(matcher.matches("--also-should-m", "--also-should-match"));
|
||||
assert!(matcher.matches("-also-should-m", "-also-should-match"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_case_insensitive_matches() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(matcher.matches("thisshould", "Thisshouldmatch"));
|
||||
assert!(matcher.matches("--Shouldm", "--shouldmatch"));
|
||||
assert!(matcher.matches("-Shouldm", "-shouldmatch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_match_when_unequal() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(!matcher.matches("ashouldmatch", "Shouldnotmatch"));
|
||||
assert!(!matcher.matches("--ashouldnotmatch", "--Shouldnotmatch"));
|
||||
assert!(!matcher.matches("-ashouldnotmatch", "-Shouldnotmatch"));
|
||||
}
|
||||
}
|
28
crates/nu-cli/src/completion/matchers/case_sensitive.rs
Normal file
28
crates/nu-cli/src/completion/matchers/case_sensitive.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use crate::completion::matchers;
|
||||
|
||||
pub struct Matcher;
|
||||
|
||||
impl matchers::Matcher for Matcher {
|
||||
fn matches(&self, partial: &str, from: &str) -> bool {
|
||||
from.starts_with(partial)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn completes_case_sensitive() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
//Should match
|
||||
assert!(matcher.matches("shouldmatch", "shouldmatch"));
|
||||
assert!(matcher.matches("shouldm", "shouldmatch"));
|
||||
assert!(matcher.matches("--also-should-m", "--also-should-match"));
|
||||
assert!(matcher.matches("-also-should-m", "-also-should-match"));
|
||||
|
||||
// Should not match
|
||||
assert!(!matcher.matches("--Shouldnot", "--shouldnotmatch"));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user