mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 15:11:52 +02:00
Compare commits
815 Commits
Author | SHA1 | Date | |
---|---|---|---|
55cab9eb4f | |||
b39dda0550 | |||
7c0a52a81e | |||
318d13ed58 | |||
21a3ceee92 | |||
a8f6a13239 | |||
b9f1371994 | |||
51c685aa99 | |||
9e39284de9 | |||
26899bc0f0 | |||
a74d05061d | |||
4140834e4c | |||
bd44bcee32 | |||
1e4678f929 | |||
fe5055cf29 | |||
d9d956e54f | |||
6c2c16a971 | |||
955a5ed8fb | |||
a59414203f | |||
631b067281 | |||
02bac0a326 | |||
7c8fb060f1 | |||
04c0e94349 | |||
2a946af81e | |||
18be6768c9 | |||
fbe61a06f6 | |||
7a4d6d64fd | |||
0eae9c49b0 | |||
d0bca1fb0f | |||
7c7e5112ea | |||
ec96e85d04 | |||
d60d71a697 | |||
6f0dd8e885 | |||
de99e35106 | |||
774be79321 | |||
721f704260 | |||
2846e3f5d9 | |||
b9ca3b2039 | |||
8ac572ed27 | |||
c4163c3621 | |||
1d7c909080 | |||
500683831c | |||
9a2fe7ec0c | |||
3ae3e3d23d | |||
fc07e849fe | |||
fadcdde7f8 | |||
d056bf070f | |||
2591050fbe | |||
e8dfd4ba39 | |||
383e874166 | |||
e8a2250ef8 | |||
440e12abc4 | |||
25ba6ea459 | |||
5ec226a416 | |||
a021b99614 | |||
e9de4c08b0 | |||
2e968d2557 | |||
bc6fa85a4b | |||
4e6c2c0fa1 | |||
7eadbd938d | |||
31a5de973d | |||
94fc8a1334 | |||
aa1cd7eba6 | |||
16faafb7a8 | |||
e376c2d0d4 | |||
c4dc61425d | |||
128f5bce30 | |||
82d69305b6 | |||
57a009b8e6 | |||
a2e6f5ebdb | |||
995dbd25b3 | |||
1d0d0425d4 | |||
51890baace | |||
4bca36f479 | |||
7d78f40bf6 | |||
131b5b56d7 | |||
fcd94efbd6 | |||
13257004bc | |||
8b193db0cb | |||
5537dce3cc | |||
fd5da62c66 | |||
927578a26f | |||
290c712cde | |||
2486492c4d | |||
7bf10b980c | |||
55c243a17b | |||
df526f73be | |||
29a77fd6ae | |||
be9ebd9e18 | |||
01f1208ad1 | |||
9dbb3e80fe | |||
a5c14ba7d4 | |||
4b11b283ac | |||
6fdfc84904 | |||
bff81f24aa | |||
ed515cbc0c | |||
87a6d7166c | |||
55baee9a9a | |||
0886afe650 | |||
872f6166e1 | |||
fe348e236f | |||
e0f083d117 | |||
48171f8e24 | |||
bcdf74562b | |||
3a5ee1aed0 | |||
d8c4b9c4fb | |||
6ae7884786 | |||
41834d16d6 | |||
1ee51f2afa | |||
65ee7aa372 | |||
ac38ee82f4 | |||
5fcc7f2328 | |||
3bcc2aad80 | |||
6165b6ae77 | |||
e335e4fddc | |||
5ab4199d71 | |||
f075e2459d | |||
94a26abf21 | |||
bcbdc33049 | |||
21ef3895b3 | |||
3e99dc01b0 | |||
3e325a1974 | |||
2b92e3e8a7 | |||
cb90b90cbf | |||
9776a252ee | |||
751de20f93 | |||
28388b4e3a | |||
4fdbf30308 | |||
722f191e82 | |||
20f6114617 | |||
3075e2cfbf | |||
e2973d2176 | |||
0ff08bb63a | |||
08c0bf52bc | |||
d0229cb96e | |||
0612e5ccfb | |||
1b4f7b34c8 | |||
86e6fcd309 | |||
dc9cd7d8b9 | |||
c0cc9ce7cd | |||
be2f66397b | |||
07760b4129 | |||
d79a3130b8 | |||
440a589f9e | |||
f5fcf9d635 | |||
9b8b1bad57 | |||
874ecd6c88 | |||
57e2fec497 | |||
0905a2c3a2 | |||
3aa00b78f9 | |||
6769d46dbb | |||
efac712f62 | |||
2bb23c57df | |||
bc699a2cc1 | |||
758c128147 | |||
3795c2a39d | |||
311c0e3f50 | |||
25a8caa9b0 | |||
c80a9585b0 | |||
e73491441a | |||
b93b80ccaa | |||
48128c9db6 | |||
6dafaa197d | |||
1634d8e087 | |||
7a583083b8 | |||
75156ab0c9 | |||
9fd6923821 | |||
91a929b2a9 | |||
0f8e31af06 | |||
bd71c2f34d | |||
001123dbd6 | |||
cfee151d4e | |||
fc59291191 | |||
4fc05cac56 | |||
cc4616f25b | |||
e82fbb7bcf | |||
8cd639f6a2 | |||
a8f555856a | |||
3792562046 | |||
d05c48a1d7 | |||
36cc5eb933 | |||
f9f74a0f7d | |||
77f42931ff | |||
73f62266c6 | |||
df2f3d25b0 | |||
599c43ce04 | |||
5c2199e7f4 | |||
3ad4e0348f | |||
02d5729941 | |||
ce35689d2e | |||
da81e21bf2 | |||
3b2ed7631f | |||
1a46e70dfb | |||
0fc9b6cfa2 | |||
61768aa2fd | |||
ea5bf9db36 | |||
9d24afcfe3 | |||
033df9457b | |||
d8e105fe34 | |||
d34068da18 | |||
611103d211 | |||
528c1c5fd8 | |||
f73732bf1e | |||
fd7875e572 | |||
e8bc319f08 | |||
a92ff57270 | |||
004230d02d | |||
a148c640b2 | |||
ea0205f2ff | |||
005649b6fc | |||
e09e3b01d6 | |||
fc15e0e27d | |||
b2fe5fabb1 | |||
52d69bb021 | |||
5f550a355b | |||
dbecbdccd4 | |||
734877338d | |||
2e439ca77f | |||
a853880e07 | |||
93f3ed98e1 | |||
a131eddf54 | |||
b19a7aa8a6 | |||
f5aa53c530 | |||
80f5e14512 | |||
41390cd963 | |||
0b5e131410 | |||
556596bce8 | |||
b791d1ab0d | |||
ac070ae942 | |||
111ad868a7 | |||
a7274115d0 | |||
81160bcefb | |||
2880109f31 | |||
09a1f5acb9 | |||
5fcf11fcb0 | |||
42fac722bb | |||
073e5727c6 | |||
ad1c4f5e39 | |||
dc8a68c98f | |||
e5621dea58 | |||
00acf22f5f | |||
4c09716ad8 | |||
1c941557c3 | |||
28e1a7915d | |||
4bc9d9fd3b | |||
e278ca61d1 | |||
2146ede15d | |||
e737222a5d | |||
f03f1949bf | |||
0fe6c7c558 | |||
b13202bbfc | |||
90fae903ce | |||
06b154f4b2 | |||
419a0665c8 | |||
387098fc87 | |||
c42b588782 | |||
4faaa5310e | |||
c448abd44e | |||
2517588d7d | |||
8fc8fc89aa | |||
b243b3ee1d | |||
28e08afada | |||
7e184b58b2 | |||
589fc0b8ad | |||
f0c7c1b500 | |||
840bd98e01 | |||
a5cdd22bfe | |||
0c7bcae9b1 | |||
ab666c170c | |||
d2213d18fa | |||
82b6300dcb | |||
b69cda9e07 | |||
56adc7c3c6 | |||
2ace20fade | |||
c13fe83784 | |||
6cf8df8685 | |||
86a89404be | |||
0d305d7c3e | |||
ee5bd2b4b3 | |||
22ae962b57 | |||
864139d67f | |||
1dc7e00d20 | |||
49a9107e0f | |||
7b8c2c232f | |||
15e1e6376b | |||
06d9d9ed08 | |||
74e10d6f72 | |||
d43489a6a0 | |||
983de8974b | |||
c91a1ec08d | |||
507de45d40 | |||
fe0fc8d5e1 | |||
e4a8db56f9 | |||
1d1ec4727a | |||
0b71e45072 | |||
9c375b33a6 | |||
28a6a5ea57 | |||
f83ff0e47d | |||
079e575cac | |||
6b2327f231 | |||
596608aa0c | |||
120e80d1b6 | |||
aa6c6120f6 | |||
19d5f782cc | |||
84169a91ff | |||
d1c48cdcf9 | |||
dfe95d3ae6 | |||
57ebec385f | |||
7a77910720 | |||
23d8dc959c | |||
7f303a856e | |||
e834e617f3 | |||
2c89a228d5 | |||
803826cdcd | |||
42d18d2294 | |||
b5ae024cc8 | |||
5968811441 | |||
fc59c87606 | |||
7dc1d6a350 | |||
deff1aa63b | |||
08e7d0dfb6 | |||
892aae267d | |||
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 |
@ -9,21 +9,18 @@ strategy:
|
||||
linux-minimal:
|
||||
image: ubuntu-18.04
|
||||
style: 'minimal'
|
||||
linux-extra:
|
||||
image: ubuntu-18.04
|
||||
style: 'extra'
|
||||
linux-wasm:
|
||||
image: ubuntu-18.04
|
||||
style: 'wasm'
|
||||
macos-stable:
|
||||
image: macos-10.14
|
||||
style: 'unflagged'
|
||||
windows-stable:
|
||||
image: windows-2019
|
||||
style: 'unflagged'
|
||||
linux-nightly-canary:
|
||||
image: ubuntu-18.04
|
||||
style: 'canary'
|
||||
macos-nightly-canary:
|
||||
image: macos-10.14
|
||||
style: 'canary'
|
||||
windows-nightly-canary:
|
||||
image: windows-2019
|
||||
style: 'canary'
|
||||
fmt:
|
||||
image: ubuntu-18.04
|
||||
style: 'fmt'
|
||||
@ -37,6 +34,7 @@ steps:
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
sudo npm install -g wasm-pack
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
@ -49,21 +47,21 @@ steps:
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
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
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
|
||||
- bash: cd samples/wasm && wasm-pack build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
||||
|
@ -1 +0,0 @@
|
||||
[build]
|
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"]
|
@ -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
|
||||
|
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -12,9 +12,9 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
@ -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.**
|
||||
|
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -112,7 +112,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -129,7 +129,7 @@ jobs:
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_stable_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
|
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
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
||||
|
||||
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
|
||||
|
||||
@ -25,38 +25,51 @@ cargo build
|
||||
|
||||
### Useful Commands
|
||||
|
||||
Build and run Nushell:
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
Run Clippy on Nushell:
|
||||
- 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
|
||||
```
|
||||
|
5908
Cargo.lock
generated
5908
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
278
Cargo.toml
278
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.17.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,92 +18,151 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.17.0", path = "./crates/nu-cli"}
|
||||
nu-errors = {version = "0.17.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.17.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.17.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.17.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.17.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.17.0", path = "./crates/nu-value-ext"}
|
||||
nu_plugin_binaryview = {version = "0.17.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.17.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.17.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.17.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.17.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.17.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.17.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_start = {version = "0.17.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.17.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.17.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.17.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.17.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.17.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu-cli = { version="0.33.0", path="./crates/nu-cli", default-features=false }
|
||||
nu-command = { version="0.33.0", path="./crates/nu-command" }
|
||||
nu-completion = { version="0.33.0", path="./crates/nu-completion" }
|
||||
nu-data = { version="0.33.0", path="./crates/nu-data" }
|
||||
nu-engine = { version="0.33.0", path="./crates/nu-engine" }
|
||||
nu-errors = { version="0.33.0", path="./crates/nu-errors" }
|
||||
nu-parser = { version="0.33.0", path="./crates/nu-parser" }
|
||||
nu-path = { version="0.33.0", path="./crates/nu-path" }
|
||||
nu-plugin = { version="0.33.0", path="./crates/nu-plugin" }
|
||||
nu-protocol = { version="0.33.0", path="./crates/nu-protocol" }
|
||||
nu-source = { version="0.33.0", path="./crates/nu-source" }
|
||||
nu-value-ext = { version="0.33.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.33.0", path="./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_chart = { version="0.33.0", path="./crates/nu_plugin_chart", optional=true }
|
||||
nu_plugin_fetch = { version="0.33.0", path="./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_from_bson = { version="0.33.0", path="./crates/nu_plugin_from_bson", optional=true }
|
||||
nu_plugin_from_sqlite = { version="0.33.0", path="./crates/nu_plugin_from_sqlite", optional=true }
|
||||
nu_plugin_inc = { version="0.33.0", path="./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version="0.33.0", path="./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version="0.33.0", path="./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version="0.33.0", path="./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_query_json = { version="0.33.0", path="./crates/nu_plugin_query_json", optional=true }
|
||||
nu_plugin_s3 = { version="0.33.0", path="./crates/nu_plugin_s3", optional=true }
|
||||
nu_plugin_selector = { version="0.33.0", path="./crates/nu_plugin_selector", optional=true }
|
||||
nu_plugin_start = { version="0.33.0", path="./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_sys = { version="0.33.0", path="./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version="0.33.0", path="./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_to_bson = { version="0.33.0", path="./crates/nu_plugin_to_bson", optional=true }
|
||||
nu_plugin_to_sqlite = { version="0.33.0", path="./crates/nu_plugin_to_sqlite", optional=true }
|
||||
nu_plugin_tree = { version="0.33.0", path="./crates/nu_plugin_tree", optional=true }
|
||||
nu_plugin_xpath = { version="0.33.0", path="./crates/nu_plugin_xpath", optional=true }
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.1"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
pretty_env_logger = "0.4.0"
|
||||
starship = "0.43.0"
|
||||
# Required to bootstrap the main binary
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.17.0", path = "./crates/nu-test-support"}
|
||||
nu-test-support = { version="0.33.0", path="./crates/nu-test-support" }
|
||||
dunce = "1.0.1"
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = {version = "0.17.0", path = "./crates/nu-build"}
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
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/which", "nu-command/which", "nu-engine/which"]
|
||||
executable-support = ["nu-completion/is_executable"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
"textview",
|
||||
"inc",
|
||||
"git-support",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"nu-cli/shadow-rs",
|
||||
"sys",
|
||||
"ps",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"term-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"zip-support",
|
||||
"executable-support",
|
||||
]
|
||||
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
|
||||
|
||||
# Default
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"inc",
|
||||
"tree",
|
||||
"ptree-support",
|
||||
"textview",
|
||||
"clipboard-cli",
|
||||
"trash-support",
|
||||
"uuid-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
"s3",
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
"query-json",
|
||||
]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
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"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli", "nu-command/clipboard-cli"]
|
||||
query-json = ["nu_plugin_query_json"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
trash-support = [
|
||||
"nu-cli/trash-support",
|
||||
"nu-command/trash-support",
|
||||
"nu-engine/trash-support",
|
||||
]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
#This is disabled in extra for now
|
||||
table-pager = ["nu-command/table-pager"]
|
||||
|
||||
#dataframe feature for nushell
|
||||
dataframe = [
|
||||
"nu-engine/dataframe",
|
||||
"nu-protocol/dataframe",
|
||||
"nu-command/dataframe",
|
||||
"nu-value-ext/dataframe",
|
||||
"nu-data/dataframe",
|
||||
"nu_plugin_post/dataframe",
|
||||
"nu_plugin_to_bson/dataframe",
|
||||
]
|
||||
|
||||
|
||||
[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
|
||||
# debug = true
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -128,37 +187,88 @@ 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_query_json"
|
||||
path = "src/plugins/nu_plugin_extra_query_json.rs"
|
||||
required-features = ["query-json"]
|
||||
|
||||
[[bin]]
|
||||
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"
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2020 Yehuda Katz, Jonathan Turner
|
||||
Copyright (c) 2019 - 2021 Yehuda Katz, Jonathan Turner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
137
README.md
137
README.md
@ -1,17 +1,18 @@
|
||||
# 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://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||

|
||||

|
||||
|
||||
## Nu Shell
|
||||
## Nushell
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
## Status
|
||||
|
||||
@ -34,7 +35,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
|
||||
|
||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
||||
|
||||
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.
|
||||
|
||||
@ -44,30 +45,36 @@ Try it in Gitpod.
|
||||
|
||||
### 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.51 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
|
||||
```shell
|
||||
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:
|
||||
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
||||
|
||||
```bash
|
||||
cargo build --workspace --features=stable
|
||||
```shell
|
||||
winget 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/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```shell
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
### Docker
|
||||
@ -76,7 +83,7 @@ cargo build --workspace --features=stable
|
||||
|
||||
Want to try Nu right away? Execute the following to get started.
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker run -it quay.io/nushell/nu:latest
|
||||
```
|
||||
|
||||
@ -85,30 +92,30 @@ docker run -it quay.io/nushell/nu:latest
|
||||
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
|
||||
```shell
|
||||
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`
|
||||
Both "nu-base" and "nu" provide the nu binary, however, nu-base also includes the source code at `/code`
|
||||
in the container and all dependencies.
|
||||
|
||||
Optionally, you can also build the containers locally using the [dockerfiles provided](docker):
|
||||
To build the base image:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
```
|
||||
|
||||
And then to build the smaller container (using a Multistage build):
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker build -f docker/Dockerfile -t nushell/nu .
|
||||
```
|
||||
|
||||
Either way, you can run either container as follows:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker run -it nushell/nu-base
|
||||
docker run -it nushell/nu
|
||||
/> exit
|
||||
@ -135,13 +142,13 @@ These values can be piped through a series of steps, in a series of commands cal
|
||||
|
||||
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_.
|
||||
Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin.
|
||||
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
||||
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 == "Dir"`)
|
||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `autoview`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
@ -170,7 +177,7 @@ We could have also written the above:
|
||||
```
|
||||
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
||||
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
||||
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
@ -187,14 +194,14 @@ For example, we could use the built-in `ps` command as well to get a list of the
|
||||
|
||||
### Opening files
|
||||
|
||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
||||
Nu can load file and URL contents as raw text or structured data (if it recognizes the format).
|
||||
For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```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]
|
||||
@ -219,36 +226,36 @@ 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.15.1
|
||||
version │ 0.32.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version | echo $it
|
||||
0.15.1
|
||||
> open Cargo.toml | get package.version
|
||||
0.32.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
|
||||
### Configuration
|
||||
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
|
||||
|
||||
To set one of these variables, you can use `config --set`. For example:
|
||||
To set one of these variables, you can use `config set`. For example:
|
||||
|
||||
```shell
|
||||
> config --set [edit_mode "vi"]
|
||||
> config --set [path $nu.path]
|
||||
> config set line_editor.edit_mode "vi"
|
||||
> config set path $nu.path
|
||||
```
|
||||
|
||||
### 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.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
|
||||
|
||||
To do so, use the `enter` command, which will allow you create a new "shell" and enter it at the specified path.
|
||||
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
|
||||
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
|
||||
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
|
||||
|
||||
@ -262,7 +269,7 @@ This allows you to extend nu for your needs.
|
||||
There are a few examples in the `plugins` directory.
|
||||
|
||||
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
|
||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use.
|
||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
|
||||
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.
|
||||
|
||||
@ -270,49 +277,55 @@ If the plugin is a sink, it is given the full vector of final data and is given
|
||||
|
||||
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 consistent first-class 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
|
||||
|
||||
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
|
||||
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
|
||||
| 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 | | | Aliases allow for shortening large commands, while passing flags |
|
||||
| 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 and scoped environment 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 updated to latest release, including usage examples |
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | | X | | | Functions and aliases are supported |
|
||||
| Variables | | | X | | | Nu supports variables and environment variables |
|
||||
| Completions | | | X | | | Completions for filepaths |
|
||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
||||
|
||||
## Current Roadmap
|
||||
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release and 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.
|
||||
|
||||
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.
|
||||
|
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()
|
||||
}
|
13
crates/README.md
Normal file
13
crates/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Nushell core libraries and plugins
|
||||
|
||||
These sub-crates form both the foundation for Nu and a set of plugins which extend Nu with additional functionality.
|
||||
|
||||
Foundational libraries are split into two kinds of crates:
|
||||
|
||||
* Core crates - those crates that work together to build the Nushell language engine
|
||||
* Support crates - a set of crates that support the engine with additional features like JSON support, ANSI support, and more.
|
||||
|
||||
Plugins are likewise also split into two types:
|
||||
|
||||
* Core plugins - plugins that provide part of the default experience of Nu, including access to the system properties, processes, and web-connectivity features.
|
||||
* Extra plugins - these plugins run a wide range of differnt capabilities like working with different file types, charting, viewing binary data, and more.
|
2
crates/nu-ansi-term/.gitignore
vendored
Normal file
2
crates/nu-ansi-term/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
40
crates/nu-ansi-term/Cargo.toml
Normal file
40
crates/nu-ansi-term/Cargo.toml
Normal file
@ -0,0 +1,40 @@
|
||||
[package]
|
||||
authors = [
|
||||
"ogham@bsago.me",
|
||||
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
|
||||
"Josh Triplett <josh@joshtriplett.org>",
|
||||
"The Nu Project Contributors",
|
||||
]
|
||||
description = "Library for ANSI terminal colors and styles (bold, underline)"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-ansi-term"
|
||||
version = "0.33.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
# name = "nu-ansi-term"
|
||||
|
||||
[features]
|
||||
derive_serde_style = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
overload = "0.1.1"
|
||||
serde = { version="1.0.90", features=["derive"], optional=true }
|
||||
itertools = "0.10.0"
|
||||
|
||||
# [dependencies.serde]
|
||||
# version = "1.0.90"
|
||||
# features = ["derive"]
|
||||
# optional = true
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
regex = "1.1.9"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.39"
|
21
crates/nu-ansi-term/LICENCE
Normal file
21
crates/nu-ansi-term/LICENCE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Benjamin Sago
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
182
crates/nu-ansi-term/README.md
Normal file
182
crates/nu-ansi-term/README.md
Normal file
@ -0,0 +1,182 @@
|
||||
# nu-ansi-term
|
||||
|
||||
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
|
||||
|
||||
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
nu_ansi_term = "0.13"
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
|
||||
|
||||
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
|
||||
The `Color` enum represents the available colors.
|
||||
And an `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
`Color` is also available as an alias to `Color`.
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
|
||||
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
|
||||
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
|
||||
|
||||
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
let red_string = Red.paint("a red string").to_string();
|
||||
```
|
||||
|
||||
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
|
||||
|
||||
```rust,ignore
|
||||
let enabled = nu_ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
|
||||
Each method creates a new style that has that specific property set.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
|
||||
println!("How about some {} and {}?",
|
||||
Style::new().bold().paint("bold"),
|
||||
Style::new().underline().paint("underline"));
|
||||
```
|
||||
|
||||
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::{Blue, Yellow};
|
||||
|
||||
println!("Demonstrating {} and {}!",
|
||||
Blue.bold().paint("blue bold"),
|
||||
Yellow.underline().paint("yellow underline"));
|
||||
|
||||
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
```
|
||||
|
||||
The complete list of styles you can use are:
|
||||
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
|
||||
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
```
|
||||
|
||||
You can turn a `Color` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
## Extended colors
|
||||
|
||||
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
|
||||
This can be included wherever you would use a `Color`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Fixed;
|
||||
|
||||
Fixed(134).paint("A sort of light purple");
|
||||
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
```
|
||||
|
||||
The first sixteen of these values are the same as the normal and bold standard color variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colors instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::RGB;
|
||||
|
||||
RGB(70, 130, 180).paint("Steel blue");
|
||||
```
|
||||
|
||||
## Combining successive coloured strings
|
||||
|
||||
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
|
||||
For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
|
||||
|
||||
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
|
||||
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
|
||||
|
||||
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
|
||||
let some_value = format!("{:b}", 42);
|
||||
let strings: &[ANSIString<'static>] = &[
|
||||
Red.paint("["),
|
||||
Red.bold().paint(some_value),
|
||||
Red.paint("]"),
|
||||
];
|
||||
|
||||
println!("Value: {}", ANSIStrings(strings));
|
||||
```
|
||||
|
||||
There are several things to note here.
|
||||
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
|
||||
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
|
||||
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
|
||||
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
|
||||
|
||||
## Byte strings
|
||||
|
||||
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
|
||||
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
|
||||
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
|
||||
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
||||
|
||||
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
use nu_ansi_term::ANSIByteStrings;
|
||||
|
||||
ANSIByteStrings(&[
|
||||
Green.paint("user data 1\n".as_bytes()),
|
||||
Green.bold().paint("user data 2\n".as_bytes()),
|
||||
]).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
72
crates/nu-ansi-term/examples/256_colors.rs
Normal file
72
crates/nu-ansi-term/examples/256_colors.rs
Normal file
@ -0,0 +1,72 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
// This example prints out the 256 colors.
|
||||
// They're arranged like this:
|
||||
//
|
||||
// - 0 to 8 are the eight standard colors.
|
||||
// - 9 to 15 are the eight bold colors.
|
||||
// - 16 to 231 are six blocks of six-by-six color squares.
|
||||
// - 232 to 255 are shades of grey.
|
||||
|
||||
fn main() {
|
||||
// First two lines
|
||||
for c in 0..8 {
|
||||
glow(c, c != 0);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 8..16 {
|
||||
glow(c, c != 8);
|
||||
print!(" ");
|
||||
}
|
||||
println!("\n");
|
||||
|
||||
// Six lines of the first three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(16 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// Six more lines of the other three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(124 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// The last greyscale lines
|
||||
for c in 232..=243 {
|
||||
glow(c, false);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 244..=255 {
|
||||
glow(c, true);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn glow(c: u8, light_bg: bool) {
|
||||
let base = if light_bg { Color::Black } else { Color::White };
|
||||
let style = base.on(Color::Fixed(c));
|
||||
print!("{}", style.paint(&format!(" {:3} ", c)));
|
||||
}
|
18
crates/nu-ansi-term/examples/basic_colors.rs
Normal file
18
crates/nu-ansi-term/examples/basic_colors.rs
Normal file
@ -0,0 +1,18 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color::*, Style};
|
||||
|
||||
// This example prints out the 16 basic colors.
|
||||
|
||||
fn main() {
|
||||
let normal = Style::default();
|
||||
|
||||
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
|
||||
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
|
||||
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
|
||||
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
|
||||
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
|
||||
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
|
||||
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
|
||||
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
|
||||
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
|
||||
}
|
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
|
||||
|
||||
fn main() {
|
||||
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
|
||||
|
||||
// a gradient from hex colors
|
||||
let start = Rgb::from_hex(0x40c9ff);
|
||||
let end = Rgb::from_hex(0xe81cff);
|
||||
let grad0 = Gradient::new(start, end);
|
||||
|
||||
// a gradient from color::rgb()
|
||||
let start = Color::Rgb(64, 201, 255);
|
||||
let end = Color::Rgb(232, 28, 255);
|
||||
let gradient = Gradient::from_color_rgb(start, end);
|
||||
|
||||
// a slightly different gradient
|
||||
let start2 = Color::Rgb(128, 64, 255);
|
||||
let end2 = Color::Rgb(0, 28, 255);
|
||||
let gradient2 = Gradient::from_color_rgb(start2, end2);
|
||||
|
||||
// reverse the gradient
|
||||
let gradient3 = gradient.reverse();
|
||||
|
||||
let build_fg = gradient.build(text, TargetGround::Foreground);
|
||||
println!("{}", build_fg);
|
||||
let build_bg = gradient.build(text, TargetGround::Background);
|
||||
println!("{}", build_bg);
|
||||
let bgt = build_all_gradient_text(text, gradient, gradient2);
|
||||
println!("{}", bgt);
|
||||
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
|
||||
println!("{}", bgt2);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
grad0.build("nushell is awesome", TargetGround::Foreground)
|
||||
);
|
||||
}
|
23
crates/nu-ansi-term/examples/rgb_colors.rs
Normal file
23
crates/nu-ansi-term/examples/rgb_colors.rs
Normal file
@ -0,0 +1,23 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
// This example prints out a color gradient in a grid by calculating each
|
||||
// character’s red, green, and blue components, and using 24-bit color codes
|
||||
// to display them.
|
||||
|
||||
const WIDTH: i32 = 80;
|
||||
const HEIGHT: i32 = 24;
|
||||
|
||||
fn main() {
|
||||
for row in 0..HEIGHT {
|
||||
for col in 0..WIDTH {
|
||||
let r = (row * 255 / HEIGHT) as u8;
|
||||
let g = (col * 255 / WIDTH) as u8;
|
||||
let b = 128;
|
||||
|
||||
print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" "));
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
405
crates/nu-ansi-term/src/ansi.rs
Normal file
405
crates/nu-ansi-term/src/ansi.rs
Normal file
@ -0,0 +1,405 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::fmt;
|
||||
|
||||
impl Style {
|
||||
/// Write any bytes that go *before* a piece of text to the given writer.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
// If there are actually no styles here, then don’t write *any* codes
|
||||
// as the prefix. An empty ANSI code may not affect the terminal
|
||||
// output at all, but a user may just want a code-free string.
|
||||
if self.is_plain() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Write the codes’ prefix, then write numbers, separated by
|
||||
// semicolons, for each text style we want to apply.
|
||||
write!(f, "\x1B[")?;
|
||||
let mut written_anything = false;
|
||||
|
||||
{
|
||||
let mut write_char = |c| {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
write!(f, "{}", c)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if self.is_bold {
|
||||
write_char('1')?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_char('2')?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_char('3')?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_char('4')?
|
||||
}
|
||||
if self.is_blink {
|
||||
write_char('5')?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_char('7')?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_char('8')?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_char('9')?
|
||||
}
|
||||
}
|
||||
|
||||
// The foreground and background colors, if specified, need to be
|
||||
// handled specially because the number codes are more complicated.
|
||||
// (see `write_background_code` and `write_foreground_code`)
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
bg.write_background_code(f)?;
|
||||
}
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
fg.write_foreground_code(f)?;
|
||||
}
|
||||
|
||||
// All the codes end with an `m`, because reasons.
|
||||
write!(f, "m")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write any bytes that go *after* a piece of text to the given writer.
|
||||
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
if self.is_plain() {
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "{}", RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The code to send to reset all styles and return to `Style::default()`.
|
||||
pub static RESET: &str = "\x1B[0m";
|
||||
|
||||
impl Color {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Color::Black => write!(f, "30"),
|
||||
Color::Red => write!(f, "31"),
|
||||
Color::Green => write!(f, "32"),
|
||||
Color::Yellow => write!(f, "33"),
|
||||
Color::Blue => write!(f, "34"),
|
||||
Color::Purple => write!(f, "35"),
|
||||
Color::Magenta => write!(f, "35"),
|
||||
Color::Cyan => write!(f, "36"),
|
||||
Color::White => write!(f, "37"),
|
||||
Color::Fixed(num) => write!(f, "38;5;{}", &num),
|
||||
Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
|
||||
Color::DarkGray => write!(f, "90"),
|
||||
Color::LightRed => write!(f, "91"),
|
||||
Color::LightGreen => write!(f, "92"),
|
||||
Color::LightYellow => write!(f, "93"),
|
||||
Color::LightBlue => write!(f, "94"),
|
||||
Color::LightPurple => write!(f, "95"),
|
||||
Color::LightMagenta => write!(f, "95"),
|
||||
Color::LightCyan => write!(f, "96"),
|
||||
Color::LightGray => write!(f, "97"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Color::Black => write!(f, "40"),
|
||||
Color::Red => write!(f, "41"),
|
||||
Color::Green => write!(f, "42"),
|
||||
Color::Yellow => write!(f, "43"),
|
||||
Color::Blue => write!(f, "44"),
|
||||
Color::Purple => write!(f, "45"),
|
||||
Color::Magenta => write!(f, "45"),
|
||||
Color::Cyan => write!(f, "46"),
|
||||
Color::White => write!(f, "47"),
|
||||
Color::Fixed(num) => write!(f, "48;5;{}", &num),
|
||||
Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
|
||||
Color::DarkGray => write!(f, "100"),
|
||||
Color::LightRed => write!(f, "101"),
|
||||
Color::LightGreen => write!(f, "102"),
|
||||
Color::LightYellow => write!(f, "103"),
|
||||
Color::LightBlue => write!(f, "104"),
|
||||
Color::LightPurple => write!(f, "105"),
|
||||
Color::LightMagenta => write!(f, "105"),
|
||||
Color::LightCyan => write!(f, "106"),
|
||||
Color::LightGray => write!(f, "107"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::infix`](struct.Style.html#method.infix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
impl Style {
|
||||
/// The prefix bytes for this style. These are the bytes that tell the
|
||||
/// terminal to use a different color or font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Blue};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Blue.bold();
|
||||
/// assert_eq!("\x1b[1;34m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.prefix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix bytes between this style and `next` style. These are the bytes
|
||||
/// that tell the terminal to change the style to `next`. These may include
|
||||
/// a reset followed by the next color and style, depending on the two styles.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[32m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Green.normal();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.infix(style).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Style) -> Infix {
|
||||
Infix(self, next)
|
||||
}
|
||||
|
||||
/// The suffix for this style. These are the bytes that tell the terminal
|
||||
/// to reset back to its normal color and font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Green.normal().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// The prefix bytes for this color as a `Style`. These are the bytes
|
||||
/// that tell the terminal to use a different color or font style.
|
||||
///
|
||||
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Green;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Green.suffix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix bytes between this color and `next` color. These are the bytes
|
||||
/// that tell the terminal to use the `next` color, or to do nothing if
|
||||
/// the two colors are equal.
|
||||
///
|
||||
/// See also [`Style::infix`](struct.Style.html#method.infix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::{Red, Yellow};
|
||||
///
|
||||
/// assert_eq!("\x1b[33m",
|
||||
/// Red.infix(Yellow).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Color) -> Infix {
|
||||
Infix(self.normal(), next.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this color as a `Style`. These are the bytes that
|
||||
/// tell the terminal to reset back to its normal color and font style.
|
||||
///
|
||||
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Purple;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Purple.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self.normal())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use crate::difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
}
|
||||
Difference::Reset => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.1.prefix())
|
||||
}
|
||||
Difference::Empty => {
|
||||
Ok(()) // nothing to write
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $style: expr; $input: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($style.paint($input).to_string(), $result.to_string());
|
||||
|
||||
let mut v = Vec::new();
|
||||
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
|
||||
assert_eq!(v.as_slice(), $result.as_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(plain: Style::default(); "text/plain" => "text/plain");
|
||||
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
|
||||
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
|
||||
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
|
||||
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
|
||||
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
|
||||
test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
|
||||
test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
|
||||
test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
|
||||
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
|
||||
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
|
||||
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
|
||||
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
|
||||
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
|
||||
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
|
||||
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
|
||||
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
|
||||
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
|
||||
|
||||
#[test]
|
||||
fn test_infix() {
|
||||
assert_eq!(
|
||||
Style::new().dimmed().infix(Style::new()).to_string(),
|
||||
"\x1B[0m"
|
||||
);
|
||||
assert_eq!(
|
||||
White.dimmed().infix(White.normal()).to_string(),
|
||||
"\x1B[0m\x1B[37m"
|
||||
);
|
||||
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
|
||||
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
|
||||
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
|
||||
}
|
||||
}
|
152
crates/nu-ansi-term/src/debug.rs
Normal file
152
crates/nu-ansi-term/src/debug.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::style::Style;
|
||||
use std::fmt;
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use nu_ansi_term::Color::{Red, Blue};
|
||||
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
|
||||
/// format!("{:?}", Red.on(Blue).bold().italic()));
|
||||
impl fmt::Debug for Style {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
fmt.debug_struct("Style")
|
||||
.field("foreground", &self.foreground)
|
||||
.field("background", &self.background)
|
||||
.field("blink", &self.is_blink)
|
||||
.field("bold", &self.is_bold)
|
||||
.field("dimmed", &self.is_dimmed)
|
||||
.field("hidden", &self.is_hidden)
|
||||
.field("italic", &self.is_italic)
|
||||
.field("reverse", &self.is_reverse)
|
||||
.field("strikethrough", &self.is_strikethrough)
|
||||
.field("underline", &self.is_underline)
|
||||
.finish()
|
||||
} else if self.is_plain() {
|
||||
fmt.write_str("Style {}")
|
||||
} else {
|
||||
fmt.write_str("Style { ")?;
|
||||
|
||||
let mut written_anything = false;
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "fg({:?})", fg)?
|
||||
}
|
||||
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "on({:?})", bg)?
|
||||
}
|
||||
|
||||
{
|
||||
let mut write_flag = |name| {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
fmt.write_str(name)
|
||||
};
|
||||
|
||||
if self.is_blink {
|
||||
write_flag("blink")?
|
||||
}
|
||||
if self.is_bold {
|
||||
write_flag("bold")?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_flag("dimmed")?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_flag("hidden")?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_flag("italic")?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_flag("reverse")?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_flag("strikethrough")?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_flag("underline")?
|
||||
}
|
||||
}
|
||||
|
||||
write!(fmt, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $obj: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, format!("{:?}", $obj));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(empty: style() => "Style {}");
|
||||
test!(bold: style().bold() => "Style { bold }");
|
||||
test!(italic: style().italic() => "Style { italic }");
|
||||
test!(both: style().bold().italic() => "Style { bold, italic }");
|
||||
|
||||
test!(red: Red.normal() => "Style { fg(Red) }");
|
||||
test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }");
|
||||
|
||||
test!(everything:
|
||||
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
|
||||
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
|
||||
|
||||
#[test]
|
||||
fn long_and_detailed() {
|
||||
extern crate regex;
|
||||
let expected_debug = "Style { fg(Blue), bold }";
|
||||
let expected_pretty_repat = r##"(?x)
|
||||
Style\s+\{\s+
|
||||
foreground:\s+Some\(\s+
|
||||
Blue,?\s+
|
||||
\),\s+
|
||||
background:\s+None,\s+
|
||||
blink:\s+false,\s+
|
||||
bold:\s+true,\s+
|
||||
dimmed:\s+false,\s+
|
||||
hidden:\s+false,\s+
|
||||
italic:\s+false,\s+
|
||||
reverse:\s+false,\s+
|
||||
strikethrough:\s+
|
||||
false,\s+
|
||||
underline:\s+false,?\s+
|
||||
\}"##;
|
||||
let re = regex::Regex::new(expected_pretty_repat).unwrap();
|
||||
|
||||
let style = Blue.bold();
|
||||
let style_fmt_debug = format!("{:?}", style);
|
||||
let style_fmt_pretty = format!("{:#?}", style);
|
||||
println!("style_fmt_debug:\n{}", style_fmt_debug);
|
||||
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
|
||||
|
||||
assert_eq!(expected_debug, style_fmt_debug);
|
||||
assert!(re.is_match(&style_fmt_pretty));
|
||||
}
|
||||
}
|
174
crates/nu-ansi-term/src/difference.rs
Normal file
174
crates/nu-ansi-term/src/difference.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use super::Style;
|
||||
|
||||
/// When printing out one colored string followed by another, use one of
|
||||
/// these rules to figure out which *extra* control codes need to be sent.
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Difference {
|
||||
/// Print out the control codes specified by this style to end up looking
|
||||
/// like the second string's styles.
|
||||
ExtraStyles(Style),
|
||||
|
||||
/// Converting between these two is impossible, so just send a reset
|
||||
/// command and then the second string's styles.
|
||||
Reset,
|
||||
|
||||
/// The before style is exactly the same as the after style, so no further
|
||||
/// control codes need to be printed.
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Difference {
|
||||
/// Compute the 'style difference' required to turn an existing style into
|
||||
/// the given, second style.
|
||||
///
|
||||
/// For example, to turn green text into green bold text, it's redundant
|
||||
/// to write a reset command then a second green+bold command, instead of
|
||||
/// just writing one bold command. This method should see that both styles
|
||||
/// use the foreground color green, and reduce it to a single command.
|
||||
///
|
||||
/// This method returns an enum value because it's not actually always
|
||||
/// possible to turn one style into another: for example, text could be
|
||||
/// made bold and underlined, but you can't remove the bold property
|
||||
/// without also removing the underline property. So when this has to
|
||||
/// happen, this function returns None, meaning that the entire set of
|
||||
/// styles should be reset and begun again.
|
||||
pub fn between(first: &Style, next: &Style) -> Difference {
|
||||
use self::Difference::*;
|
||||
|
||||
// XXX(Havvy): This algorithm is kind of hard to replicate without
|
||||
// having the Plain/Foreground enum variants, so I'm just leaving
|
||||
// it commented out for now, and defaulting to Reset.
|
||||
|
||||
if first == next {
|
||||
return Empty;
|
||||
}
|
||||
|
||||
// Cannot un-bold, so must Reset.
|
||||
if first.is_bold && !next.is_bold {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_dimmed && !next.is_dimmed {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_italic && !next.is_italic {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot un-underline, so must Reset.
|
||||
if first.is_underline && !next.is_underline {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_blink && !next.is_blink {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_reverse && !next.is_reverse {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_hidden && !next.is_hidden {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_strikethrough && !next.is_strikethrough {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from foreground to no foreground, so must Reset.
|
||||
if first.foreground.is_some() && next.foreground.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from background to no background, so must Reset.
|
||||
if first.background.is_some() && next.background.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
let mut extra_styles = Style::default();
|
||||
|
||||
if first.is_bold != next.is_bold {
|
||||
extra_styles.is_bold = true;
|
||||
}
|
||||
|
||||
if first.is_dimmed != next.is_dimmed {
|
||||
extra_styles.is_dimmed = true;
|
||||
}
|
||||
|
||||
if first.is_italic != next.is_italic {
|
||||
extra_styles.is_italic = true;
|
||||
}
|
||||
|
||||
if first.is_underline != next.is_underline {
|
||||
extra_styles.is_underline = true;
|
||||
}
|
||||
|
||||
if first.is_blink != next.is_blink {
|
||||
extra_styles.is_blink = true;
|
||||
}
|
||||
|
||||
if first.is_reverse != next.is_reverse {
|
||||
extra_styles.is_reverse = true;
|
||||
}
|
||||
|
||||
if first.is_hidden != next.is_hidden {
|
||||
extra_styles.is_hidden = true;
|
||||
}
|
||||
|
||||
if first.is_strikethrough != next.is_strikethrough {
|
||||
extra_styles.is_strikethrough = true;
|
||||
}
|
||||
|
||||
if first.foreground != next.foreground {
|
||||
extra_styles.foreground = next.foreground;
|
||||
}
|
||||
|
||||
if first.background != next.background {
|
||||
extra_styles.background = next.background;
|
||||
}
|
||||
|
||||
ExtraStyles(extra_styles)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Difference::*;
|
||||
use super::*;
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $first: expr; $next: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, Difference::between(&$first, &$next));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(nothing: Green.normal(); Green.normal() => Empty);
|
||||
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
|
||||
test!(lowercase: Green.bold(); Green.normal() => Reset);
|
||||
test!(nothing2: Green.bold(); Green.bold() => Empty);
|
||||
|
||||
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
|
||||
|
||||
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
|
||||
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
|
||||
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
|
||||
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
|
||||
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
|
||||
|
||||
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
|
||||
test!(removal_of_reverse: style().reverse(); style() => Reset);
|
||||
test!(removal_of_hidden: style().hidden(); style() => Reset);
|
||||
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
|
||||
test!(removal_of_blink: style().blink(); style() => Reset);
|
||||
}
|
303
crates/nu-ansi-term/src/display.rs
Normal file
303
crates/nu-ansi-term/src/display.rs
Normal file
@ -0,0 +1,303 @@
|
||||
use crate::ansi::RESET;
|
||||
use crate::difference::Difference;
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// An `ANSIGenericString` includes a generic string type and a `Style` to
|
||||
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
|
||||
/// this type on `str` and `\[u8]`, respectively.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
style: Style,
|
||||
string: Cow<'a, S>,
|
||||
}
|
||||
|
||||
/// Cloning an `ANSIGenericString` will clone its underlying string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// let clone_string = plain_string.clone();
|
||||
/// assert_eq!(clone_string, plain_string);
|
||||
/// ```
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn clone(&self) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
style: self.style,
|
||||
string: self.string.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You might think that the hand-written Clone impl above is the same as the
|
||||
// one that gets generated with #[derive]. But it’s not *quite* the same!
|
||||
//
|
||||
// `str` is not Clone, and the derived Clone implementation puts a Clone
|
||||
// constraint on the S type parameter (generated using --pretty=expanded):
|
||||
//
|
||||
// ↓_________________↓
|
||||
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
|
||||
// for ANSIGenericString<'a, S> where
|
||||
// <S as ToOwned>::Owned: fmt::Debug { ... }
|
||||
//
|
||||
// This resulted in compile errors when you tried to derive Clone on a type
|
||||
// that used it:
|
||||
//
|
||||
// #[derive(PartialEq, Debug, Clone, Default)]
|
||||
// pub struct TextCellContents(Vec<ANSIString<'static>>);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
|
||||
//
|
||||
// The hand-written impl above can ignore that constraint and still compile.
|
||||
|
||||
/// An ANSI String is a string coupled with the `Style` to display it
|
||||
/// in a terminal.
|
||||
///
|
||||
/// Although not technically a string itself, it can be turned into
|
||||
/// one with the `to_string` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
/// use nu_ansi_term::Color::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// assert_eq!(&*plain_string, "a plain string");
|
||||
/// ```
|
||||
pub type AnsiString<'a> = AnsiGenericString<'a, str>;
|
||||
|
||||
/// An `AnsiByteString` represents a formatted series of bytes. Use
|
||||
/// `AnsiByteString` when styling text with an unknown encoding.
|
||||
pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>;
|
||||
|
||||
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn from(input: I) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
/// Directly access the style
|
||||
pub fn style_ref(&self) -> &Style {
|
||||
&self.style
|
||||
}
|
||||
|
||||
/// Directly access the style mutably
|
||||
pub fn style_ref_mut(&mut self) -> &mut Style {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.string.deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of `AnsiGenericStrings`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>])
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
S: PartialEq;
|
||||
|
||||
/// A set of `AnsiString`s collected together, in order to be written with a
|
||||
/// minimum of control characters.
|
||||
pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>;
|
||||
|
||||
/// A function to construct an `AnsiStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
/// A set of `AnsiByteString`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>;
|
||||
|
||||
/// A function to construct an `ANSIByteStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
// ---- paint functions ----
|
||||
|
||||
impl Style {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for individual ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let w: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteString<'a> {
|
||||
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
|
||||
/// sequences for the associated `Style` around the bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
write!(w, "{}", self.style.prefix())?;
|
||||
w.write_str(self.string.as_ref())?;
|
||||
write!(w, "{}", self.style.suffix())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for combined ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiStrings<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteStrings<'a> {
|
||||
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
|
||||
/// escape sequences for the associated `Style`s around each set of
|
||||
/// bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
use self::Difference::*;
|
||||
|
||||
let first = match self.0.first() {
|
||||
None => return Ok(()),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
write!(w, "{}", first.style.prefix())?;
|
||||
w.write_str(first.string.as_ref())?;
|
||||
|
||||
for window in self.0.windows(2) {
|
||||
match Difference::between(&window[0].style, &window[1].style) {
|
||||
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
|
||||
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
|
||||
Empty => { /* Do nothing! */ }
|
||||
}
|
||||
|
||||
w.write_str(&window[1].string)?;
|
||||
}
|
||||
|
||||
// Write the final reset string after all of the ANSIStrings have been
|
||||
// written, *except* if the last one has no styles, because it would
|
||||
// have already been written by this point.
|
||||
if let Some(last) = self.0.last() {
|
||||
if !last.style.is_plain() {
|
||||
write!(w, "{}", RESET)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tests ----
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub use super::super::AnsiStrings;
|
||||
pub use crate::style::Color::*;
|
||||
pub use crate::style::Style;
|
||||
|
||||
#[test]
|
||||
fn no_control_codes_for_plain() {
|
||||
let one = Style::default().paint("one");
|
||||
let two = Style::default().paint("two");
|
||||
let output = format!("{}", AnsiStrings(&[one, two]));
|
||||
assert_eq!(&*output, "onetwo");
|
||||
}
|
||||
}
|
105
crates/nu-ansi-term/src/gradient.rs
Normal file
105
crates/nu-ansi-term/src/gradient.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::{rgb::Rgb, Color};
|
||||
|
||||
/// Linear color gradient between two color stops
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Gradient {
|
||||
/// Start Color of Gradient
|
||||
pub start: Rgb,
|
||||
|
||||
/// End Color of Gradient
|
||||
pub end: Rgb,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
|
||||
#[inline]
|
||||
pub const fn new(start: Rgb, end: Rgb) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
|
||||
let start_grad = match start {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
let end_grad = match end {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
|
||||
Self {
|
||||
start: start_grad,
|
||||
end: end_grad,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the [Rgb] color between `start` and `end` for `t`
|
||||
pub fn at(&self, t: f32) -> Rgb {
|
||||
self.start.lerp(self.end, t)
|
||||
}
|
||||
|
||||
/// Returns the reverse of `self`
|
||||
#[inline]
|
||||
pub const fn reverse(&self) -> Self {
|
||||
Self::new(self.end, self.start)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build(&self, text: &str, target: TargetGround) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let temp = format!(
|
||||
"\x1B[{}m{}",
|
||||
self.at(i as f32 * delta).ansi_color_code(target),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let step = i as f32 * delta;
|
||||
let temp = format!(
|
||||
"\x1B[{};{}m{}",
|
||||
foreground
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Foreground),
|
||||
background
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Background),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetGround {
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
impl TargetGround {
|
||||
#[inline]
|
||||
pub const fn code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Foreground => 30,
|
||||
Self::Background => 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ANSIColorCode {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String;
|
||||
}
|
273
crates/nu-ansi-term/src/lib.rs
Normal file
273
crates/nu-ansi-term/src/lib.rs
Normal file
@ -0,0 +1,273 @@
|
||||
//! This is a library for controlling colors and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are three main types in this crate that you need to be
|
||||
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
|
||||
//!
|
||||
//! A `Style` holds stylistic information: foreground and background colors,
|
||||
//! whether the text should be bold, or blinking, or other properties. The
|
||||
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
|
||||
//! string paired with a [`Style`].
|
||||
//!
|
||||
//! [`Color`] is also available as an alias to `Color`.
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Color`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//! ```
|
||||
//!
|
||||
//! It’s important to note that the `paint` method does *not* actually return a
|
||||
//! string with the ANSI control characters surrounding it. Instead, it returns
|
||||
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
|
||||
//! formatted, returns the characters. This allows strings to be printed with a
|
||||
//! minimum of [`String`] allocations being performed behind the scenes.
|
||||
//!
|
||||
//! If you *do* want to get at the escape codes, then you can convert the
|
||||
//! [`ANSIString`] to a string as you would any other `Display` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground color changes, you need to
|
||||
//! construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
//! You can do this by chaining methods based on a new `Style`, created with
|
||||
//! [`Style::new()`]. Each method creates a new style that has that specific
|
||||
//! property set. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//!
|
||||
//! println!("How about some {} and {}?",
|
||||
//! Style::new().bold().paint("bold"),
|
||||
//! Style::new().underline().paint("underline"));
|
||||
//! ```
|
||||
//!
|
||||
//! For brevity, these methods have also been implemented for `Color` values,
|
||||
//! so you can give your styles a foreground color without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::{Blue, Yellow};
|
||||
//!
|
||||
//! println!("Demonstrating {} and {}!",
|
||||
//! Blue.bold().paint("blue bold"),
|
||||
//! Yellow.underline().paint("yellow underline"));
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
|
||||
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
|
||||
//! background colors.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Color`.
|
||||
//! You can do this using the [`fg`] method:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! You can turn a `Color` into a `Style` with the [`normal`] method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Color` directly, but it’s useful in certain cases:
|
||||
//! for example, you may have a method that returns `Styles`, and need to
|
||||
//! represent both the “red bold” and “red, but not bold” styles with values of
|
||||
//! the same type. The `Style` struct also has a [`Default`] implementation if you
|
||||
//! want to have a style with *nothing* set.
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colors
|
||||
//!
|
||||
//! You can access the extended range of 256 colors by using the `Color::Fixed`
|
||||
//! variant, which takes an argument of the color number to use. This can be
|
||||
//! included wherever you would use a `Color`:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Fixed;
|
||||
//!
|
||||
//! Fixed(134).paint("A sort of light purple");
|
||||
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
//! ```
|
||||
//!
|
||||
//! The first sixteen of these values are the same as the normal and bold
|
||||
//! standard color variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `Color::Rgb` variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Rgb;
|
||||
//!
|
||||
//! Rgb(70, 130, 180).paint("Steel blue");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Combining successive colored strings
|
||||
//!
|
||||
//! The benefit of writing ANSI escape codes to the terminal is that they
|
||||
//! *stack*: you do not need to end every colored string with a reset code if
|
||||
//! the text that follows it is of a similar style. For example, if you want to
|
||||
//! have some blue text followed by some blue bold text, it’s possible to send
|
||||
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
|
||||
//! with a reset code without having to have an extra one between the two
|
||||
//! strings.
|
||||
//!
|
||||
//! This crate can optimise the ANSI codes that get printed in situations like
|
||||
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
|
||||
//! type takes a slice of several [`ANSIString`] values, and will iterate over
|
||||
//! each of them, printing only the codes for the styles that need to be updated
|
||||
//! as part of its formatting routine.
|
||||
//!
|
||||
//! The following code snippet uses this to enclose a binary number displayed in
|
||||
//! red bold text inside some red, but not bold, brackets:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//! use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
//!
|
||||
//! let some_value = format!("{:b}", 42);
|
||||
//! let strings: &[ANSIString<'static>] = &[
|
||||
//! Red.paint("["),
|
||||
//! Red.bold().paint(some_value),
|
||||
//! Red.paint("]"),
|
||||
//! ];
|
||||
//!
|
||||
//! println!("Value: {}", ANSIStrings(strings));
|
||||
//! ```
|
||||
//!
|
||||
//! There are several things to note here. Firstly, the [`paint`] method can take
|
||||
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
|
||||
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
|
||||
//! borrowed strings at the same time. This is used here to display a `String`,
|
||||
//! the result of the `format!` call, using the same mechanism as some
|
||||
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
|
||||
//! works in the same way as its singular counterpart, with a [`Display`]
|
||||
//! implementation that only performs the formatting when required.
|
||||
//!
|
||||
//! ## Byte strings
|
||||
//!
|
||||
//! This library also supports formatting `\[u8]` byte strings; this supports
|
||||
//! applications working with text in an unknown encoding. [`Style`] and
|
||||
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
|
||||
//! This type does not implement [`Display`], as it may not contain UTF-8, but
|
||||
//! it does provide a method [`write_to`] to write the result to any value that
|
||||
//! implements [`Write`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//!
|
||||
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
|
||||
//! [`ANSIByteString`] values with minimal escape sequences:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//! use nu_ansi_term::ANSIByteStrings;
|
||||
//!
|
||||
//! ANSIByteStrings(&[
|
||||
//! Green.paint("user data 1\n".as_bytes()),
|
||||
//! Green.bold().paint("user data 2\n".as_bytes()),
|
||||
//! ]).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
|
||||
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
|
||||
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
|
||||
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
//! [`Style`]: struct.Style.html
|
||||
//! [`Style::new()`]: struct.Style.html#method.new
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`ANSIString`]: type.ANSIString.html
|
||||
//! [`ANSIStrings`]: type.ANSIStrings.html
|
||||
//! [`ANSIByteString`]: type.ANSIByteString.html
|
||||
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
|
||||
//! [`write_to`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`paint`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`normal`]: enum.Color.html#method.normal
|
||||
//!
|
||||
//! [`bold`]: struct.Style.html#method.bold
|
||||
//! [`dimmed`]: struct.Style.html#method.dimmed
|
||||
//! [`italic`]: struct.Style.html#method.italic
|
||||
//! [`underline`]: struct.Style.html#method.underline
|
||||
//! [`blink`]: struct.Style.html#method.blink
|
||||
//! [`reverse`]: struct.Style.html#method.reverse
|
||||
//! [`hidden`]: struct.Style.html#method.hidden
|
||||
//! [`strikethrough`]: struct.Style.html#method.strikethrough
|
||||
//! [`fg`]: struct.Style.html#method.fg
|
||||
//! [`on`]: struct.Style.html#method.on
|
||||
|
||||
#![crate_name = "nu_ansi_term"]
|
||||
#![crate_type = "rlib"]
|
||||
#![crate_type = "dylib"]
|
||||
#![warn(missing_copy_implementations)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate doc_comment;
|
||||
|
||||
#[cfg(test)]
|
||||
doctest!("../README.md");
|
||||
|
||||
pub mod ansi;
|
||||
pub use ansi::{Infix, Prefix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Color, Style};
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod util;
|
||||
pub use util::*;
|
||||
|
||||
mod debug;
|
||||
|
||||
pub mod gradient;
|
||||
pub use gradient::*;
|
||||
|
||||
mod rgb;
|
||||
pub use rgb::*;
|
173
crates/nu-ansi-term/src/rgb.rs
Normal file
173
crates/nu-ansi-term/src/rgb.rs
Normal file
@ -0,0 +1,173 @@
|
||||
// Code liberally borrowed from here
|
||||
// https://github.com/navierr/coloriz
|
||||
use std::ops;
|
||||
use std::u32;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u8,
|
||||
/// Green
|
||||
pub g: u8,
|
||||
/// Blue
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Rgb {
|
||||
/// Creates a new [Rgb] color
|
||||
#[inline]
|
||||
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with a hex code
|
||||
#[inline]
|
||||
pub const fn from_hex(hex: u32) -> Self {
|
||||
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
|
||||
}
|
||||
|
||||
pub fn from_hex_string(hex: String) -> Self {
|
||||
if hex.chars().count() == 8 && hex.starts_with("0x") {
|
||||
// eprintln!("hex:{:?}", hex);
|
||||
let (_, value_string) = hex.split_at(2);
|
||||
// eprintln!("value_string:{:?}", value_string);
|
||||
let int_val = u64::from_str_radix(value_string, 16);
|
||||
match int_val {
|
||||
Ok(num) => Self::new(
|
||||
((num & 0xff0000) >> 16) as u8,
|
||||
((num & 0xff00) >> 8) as u8,
|
||||
(num & 0xff) as u8,
|
||||
),
|
||||
// Don't fail, just make the color black
|
||||
// Should we fail?
|
||||
_ => Self::new(0, 0, 0),
|
||||
}
|
||||
} else {
|
||||
// Don't fail, just make the color black.
|
||||
// Should we fail?
|
||||
Self::new(0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with three [f32] values
|
||||
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
|
||||
Self::new(
|
||||
(r.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(g.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(b.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color
|
||||
#[inline]
|
||||
pub const fn gray(x: u8) -> Self {
|
||||
Self::new(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color with a [f32] value
|
||||
pub fn gray_f32(x: f32) -> Self {
|
||||
Self::from_f32(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color from a [HSL] color
|
||||
// pub fn from_hsl(hsl: HSL) -> Self {
|
||||
// if hsl.s == 0.0 {
|
||||
// return Self::gray_f32(hsl.l);
|
||||
// }
|
||||
|
||||
// let q = if hsl.l < 0.5 {
|
||||
// hsl.l * (1.0 + hsl.s)
|
||||
// } else {
|
||||
// hsl.l + hsl.s - hsl.l * hsl.s
|
||||
// };
|
||||
// let p = 2.0 * hsl.l - q;
|
||||
// let h2c = |t: f32| {
|
||||
// let t = t.clamp(0.0, 1.0);
|
||||
// if 6.0 * t < 1.0 {
|
||||
// p + 6.0 * (q - p) * t
|
||||
// } else if t < 0.5 {
|
||||
// q
|
||||
// } else if 1.0 < 1.5 * t {
|
||||
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
|
||||
// } else {
|
||||
// p
|
||||
// }
|
||||
// };
|
||||
|
||||
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
|
||||
// }
|
||||
|
||||
/// Computes the linear interpolation between `self` and `other` for `t`
|
||||
pub fn lerp(&self, other: Self, t: f32) -> Self {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
self * (1.0 - t) + other * t
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb {
|
||||
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||
Self::new(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32)> for Rgb {
|
||||
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||
Self::from_f32(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ANSIColorCode;
|
||||
use crate::TargetGround;
|
||||
impl ANSIColorCode for Rgb {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String {
|
||||
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_add(rhs.r),
|
||||
lhs.g.saturating_add(rhs.g),
|
||||
lhs.b.saturating_add(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_sub(rhs.r),
|
||||
lhs.g.saturating_sub(rhs.g),
|
||||
lhs.b.saturating_sub(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
|
||||
Rgb::new(
|
||||
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
-(rgb: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
255 - rgb.r,
|
||||
255 - rgb.g,
|
||||
255 - rgb.b)
|
||||
}
|
||||
);
|
626
crates/nu-ansi-term/src/style.rs
Normal file
626
crates/nu-ansi-term/src/style.rs
Normal file
@ -0,0 +1,626 @@
|
||||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().bold().on(Color::Black);
|
||||
/// println!("{}", style.paint("Bold on black"));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub struct Style {
|
||||
/// The style's foreground color, if it has one.
|
||||
pub foreground: Option<Color>,
|
||||
|
||||
/// The style's background color, if it has one.
|
||||
pub background: Option<Color>,
|
||||
|
||||
/// Whether this style is bold.
|
||||
pub is_bold: bool,
|
||||
|
||||
/// Whether this style is dimmed.
|
||||
pub is_dimmed: bool,
|
||||
|
||||
/// Whether this style is italic.
|
||||
pub is_italic: bool,
|
||||
|
||||
/// Whether this style is underlined.
|
||||
pub is_underline: bool,
|
||||
|
||||
/// Whether this style is blinking.
|
||||
pub is_blink: bool,
|
||||
|
||||
/// Whether this style has reverse colors.
|
||||
pub is_reverse: bool,
|
||||
|
||||
/// Whether this style is hidden.
|
||||
pub is_hidden: bool,
|
||||
|
||||
/// Whether this style is struckthrough.
|
||||
pub is_strikethrough: bool,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Creates a new Style with no properties set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(&self) -> Style {
|
||||
Style {
|
||||
is_bold: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style {
|
||||
is_dimmed: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(&self) -> Style {
|
||||
Style {
|
||||
is_italic: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(&self) -> Style {
|
||||
Style {
|
||||
is_underline: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(&self) -> Style {
|
||||
Style {
|
||||
is_blink: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style {
|
||||
is_reverse: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style {
|
||||
is_hidden: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style {
|
||||
is_strikethrough: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().fg(Color::Yellow);
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn fg(&self, foreground: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(foreground),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().on(Color::Blue);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(&self, background: Color) -> Style {
|
||||
Style {
|
||||
background: Some(background),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// assert_eq!(true, Style::default().is_plain());
|
||||
/// assert_eq!(false, Style::default().bold().is_plain());
|
||||
/// ```
|
||||
pub fn is_plain(self) -> bool {
|
||||
self == Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
/// Returns a style with *no* properties set. Formatting text using this
|
||||
/// style returns the exact same text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
/// assert_eq!(None, Style::default().foreground);
|
||||
/// assert_eq!(None, Style::default().background);
|
||||
/// assert_eq!(false, Style::default().is_bold);
|
||||
/// assert_eq!("txt", Style::default().paint("txt").to_string());
|
||||
/// ```
|
||||
fn default() -> Style {
|
||||
Style {
|
||||
foreground: None,
|
||||
background: None,
|
||||
is_bold: false,
|
||||
is_dimmed: false,
|
||||
is_italic: false,
|
||||
is_underline: false,
|
||||
is_blink: false,
|
||||
is_reverse: false,
|
||||
is_hidden: false,
|
||||
is_strikethrough: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- colors ----
|
||||
|
||||
/// A color is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background color.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub enum Color {
|
||||
/// Color #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background color, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Color #0 (foreground code `90`, background code `100`).
|
||||
DarkGray,
|
||||
|
||||
/// Color #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Color #1 (foreground code `91`, background code `101`).
|
||||
LightRed,
|
||||
|
||||
/// Color #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Color #2 (foreground code `92`, background code `102`).
|
||||
LightGreen,
|
||||
|
||||
/// Color #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Color #3 (foreground code `93`, background code `103`).
|
||||
LightYellow,
|
||||
|
||||
/// Color #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Color #4 (foreground code `94`, background code `104`).
|
||||
LightBlue,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightPurple,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Magenta,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightMagenta,
|
||||
|
||||
/// Color #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Color #6 (foreground code `96`, background code `106`).
|
||||
LightCyan,
|
||||
|
||||
/// Color #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground color, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// Color #7 (foreground code `97`, background code `107`).
|
||||
LightGray,
|
||||
|
||||
/// A color number from 0 to 255, for use in 256-color terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colors can usually be changed in the terminal emulator.
|
||||
/// - colors 8 to 15 are brighter versions of the eight colors above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colors and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - colors 16 to 231 contain several palettes of bright colors,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - colors 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [color chart][cc].
|
||||
///
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit Rgb color, as specified by ISO-8613-3.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::White
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Returns a `Style` with the foreground color set to this color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Red.normal();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn normal(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Green.bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_bold: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Yellow.dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_dimmed: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Blue.italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_italic: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Purple.underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_underline: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// blink property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Cyan.blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_blink: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Black.reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_reverse: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::White.hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_hidden: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Fixed(244).strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_strikethrough: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// background color property set to the given color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Rgb(31, 31, 31).on(Color::White);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(self, background: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
background: Some(background),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
/// You can turn a `Color` into a `Style` with the foreground color set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
/// let green_foreground = Style::default().fg(Color::Green);
|
||||
/// assert_eq!(green_foreground, Color::Green.normal());
|
||||
/// assert_eq!(green_foreground, Color::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Color::Green));
|
||||
/// ```
|
||||
fn from(color: Color) -> Style {
|
||||
color.normal()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "derive_serde_style")]
|
||||
mod serde_json_tests {
|
||||
use super::{Color, Style};
|
||||
|
||||
#[test]
|
||||
fn color_serialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&colors).unwrap(),
|
||||
String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_deserialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
for color in colors.iter() {
|
||||
let serialized = serde_json::to_string(&color).unwrap();
|
||||
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!(color, &deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_serialization() {
|
||||
let style = Style::default();
|
||||
|
||||
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
|
||||
}
|
||||
}
|
80
crates/nu-ansi-term/src/util.rs
Normal file
80
crates/nu-ansi-term/src/util.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::display::{AnsiString, AnsiStrings};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
|
||||
pub fn sub_string<'a>(
|
||||
start: usize,
|
||||
len: usize,
|
||||
strs: &AnsiStrings<'a>,
|
||||
) -> Vec<AnsiString<'static>> {
|
||||
let mut vec = Vec::new();
|
||||
let mut pos = start;
|
||||
let mut len_rem = len;
|
||||
|
||||
for i in strs.0.iter() {
|
||||
let fragment = i.deref();
|
||||
let frag_len = fragment.len();
|
||||
if pos >= frag_len {
|
||||
pos -= frag_len;
|
||||
continue;
|
||||
}
|
||||
if len_rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let end = pos + len_rem;
|
||||
let pos_end = if end >= frag_len { frag_len } else { end };
|
||||
|
||||
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
|
||||
|
||||
if end <= frag_len {
|
||||
break;
|
||||
}
|
||||
|
||||
len_rem -= pos_end - pos;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
|
||||
pub fn unstyle(strs: &AnsiStrings) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
for i in strs.0.iter() {
|
||||
s += i.deref();
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
|
||||
pub fn unstyled_len(strs: &AnsiStrings) -> usize {
|
||||
let mut l = 0;
|
||||
for i in strs.0.iter() {
|
||||
l += i.deref().len();
|
||||
}
|
||||
l
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Color::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let l = [
|
||||
Black.paint("first"),
|
||||
Red.paint("-second"),
|
||||
White.paint("-third"),
|
||||
];
|
||||
let a = AnsiStrings(&l);
|
||||
assert_eq!(unstyle(&a), "first-second-third");
|
||||
assert_eq!(unstyled_len(&a), 18);
|
||||
|
||||
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
|
||||
assert_eq!(sub_string(3, 11, &a).as_slice(), &l2);
|
||||
}
|
||||
}
|
62
crates/nu-ansi-term/src/windows.rs
Normal file
62
crates/nu-ansi-term/src/windows.rs
Normal file
@ -0,0 +1,62 @@
|
||||
/// Enables ANSI code support on Windows 10.
|
||||
///
|
||||
/// This uses Windows API calls to alter the properties of the console that
|
||||
/// the program is running in.
|
||||
///
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
|
||||
///
|
||||
/// Returns a `Result` with the Windows error code if unsuccessful.
|
||||
#[cfg(windows)]
|
||||
pub fn enable_ansi_support() -> Result<(), u32> {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::null_mut;
|
||||
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
|
||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
|
||||
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
unsafe {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
|
||||
let console_out_name: Vec<u16> =
|
||||
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
|
||||
let console_handle = CreateFileW(
|
||||
console_out_name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_WRITE,
|
||||
null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
if console_handle == INVALID_HANDLE_VALUE {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut console_mode: u32 = 0;
|
||||
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// VT processing not already enabled?
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
if 0 == SetConsoleMode(
|
||||
console_handle,
|
||||
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
37
crates/nu-ansi-term/src/write.rs
Normal file
37
crates/nu-ansi-term/src/write.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
pub trait AnyWrite {
|
||||
type Wstr: ?Sized;
|
||||
type Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn fmt::Write + 'a {
|
||||
type Wstr = str;
|
||||
type Error = fmt::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_str(self, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn io::Write + 'a {
|
||||
type Wstr = [u8];
|
||||
type Error = io::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
io::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
io::Write::write_all(self, s)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "Core build system for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-build"
|
||||
version = "0.17.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
serde_json = "1.0.55"
|
||||
toml = "0.5.6"
|
@ -1,80 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref WORKSPACES: Mutex<BTreeMap<String, &'static Path>> = Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
// got from https://github.com/mitsuhiko/insta/blob/b113499249584cb650150d2d01ed96ee66db6b30/src/runtime.rs#L67-L88
|
||||
|
||||
fn get_cargo_workspace(manifest_dir: &str) -> Result<Option<&Path>, Box<dyn std::error::Error>> {
|
||||
let mut workspaces = WORKSPACES.lock()?;
|
||||
if let Some(rv) = workspaces.get(manifest_dir) {
|
||||
Ok(Some(rv))
|
||||
} else {
|
||||
#[derive(Deserialize)]
|
||||
struct Manifest {
|
||||
workspace_root: String,
|
||||
}
|
||||
let output = std::process::Command::new(env!("CARGO"))
|
||||
.arg("metadata")
|
||||
.arg("--format-version=1")
|
||||
.current_dir(manifest_dir)
|
||||
.output()?;
|
||||
let manifest: Manifest = serde_json::from_slice(&output.stdout)?;
|
||||
let path = Box::leak(Box::new(PathBuf::from(manifest.workspace_root)));
|
||||
workspaces.insert(manifest_dir.to_string(), path.as_path());
|
||||
Ok(workspaces.get(manifest_dir).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Feature {
|
||||
#[allow(unused)]
|
||||
description: String,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = env::var("CARGO_MANIFEST_DIR")?;
|
||||
|
||||
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
|
||||
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
|
||||
.map(|s| s.split(',').map(|s| s.to_string()).collect())
|
||||
.unwrap_or_else(|_| HashSet::new());
|
||||
|
||||
if all_on && !flags.is_empty() {
|
||||
println!(
|
||||
"cargo:warning=Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
|
||||
);
|
||||
}
|
||||
|
||||
let workspace = match get_cargo_workspace(&input)? {
|
||||
// If the crate is being downloaded from crates.io, it won't have a workspace root, and that's ok
|
||||
None => return Ok(()),
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
let path = Path::new(&workspace).join("features.toml");
|
||||
|
||||
// If the crate is being downloaded from crates.io, it won't have a features.toml, and that's ok
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
|
||||
|
||||
for (key, value) in toml.iter() {
|
||||
if value.enabled || all_on || flags.contains(key) {
|
||||
println!("cargo:rustc-cfg={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,101 +1,108 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.17.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = {version = "0.17.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.17.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.17.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.17.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.17.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.17.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.17.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.17.0", path = "../nu-value-ext"}
|
||||
nu-completion = { version="0.33.0", path="../nu-completion" }
|
||||
nu-command = { version="0.33.0", path="../nu-command" }
|
||||
nu-data = { version="0.33.0", path="../nu-data" }
|
||||
nu-engine = { version="0.33.0", path="../nu-engine" }
|
||||
nu-errors = { version="0.33.0", path="../nu-errors" }
|
||||
nu-json = { version="0.33.0", path="../nu-json" }
|
||||
nu-parser = { version="0.33.0", path="../nu-parser" }
|
||||
nu-plugin = { version="0.33.0", path="../nu-plugin" }
|
||||
nu-protocol = { version="0.33.0", path="../nu-protocol" }
|
||||
nu-source = { version="0.33.0", path="../nu-source" }
|
||||
nu-stream = { version="0.33.0", path="../nu-stream" }
|
||||
nu-table = { version="0.33.0", path="../nu-table" }
|
||||
nu-test-support = { version="0.33.0", path="../nu-test-support" }
|
||||
nu-value-ext = { version="0.33.0", path="../nu-value-ext" }
|
||||
nu-ansi-term = { version="0.33.0", path="../nu-ansi-term" }
|
||||
nu-pretty-hex = { version="0.33.0", path="../nu-pretty-hex" }
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.36"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
chrono = {version = "0.4.11", features = ["serde"]}
|
||||
clap = "2.33.1"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1"
|
||||
ctrlc = {version = "3.1.4", optional = true}
|
||||
Inflector = "0.11"
|
||||
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"
|
||||
codespan-reporting = "0.11.0"
|
||||
csv = "1.1.5"
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "2.0.2", optional = true}
|
||||
dirs = {version = "2.0.2", optional = true}
|
||||
directories-next = "2.0.0"
|
||||
dirs-next = "2.0.0"
|
||||
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, optional = true}
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.4.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.11"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
ical = "0.7.0"
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
ptree = {version = "0.2", optional = true}
|
||||
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"
|
||||
ptree = { version="0.3.1", optional=true }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.13.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
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="8.1.0", optional=true }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.5.2", optional = true}
|
||||
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.0"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.1"
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.1", optional = true}
|
||||
|
||||
clipboard = {version = "0.5", optional = true}
|
||||
encoding_rs = "0.8.23"
|
||||
rayon = "1.3.1"
|
||||
starship = {version = "0.43.0", optional = true}
|
||||
trash = {version = "1.0.1", optional = true}
|
||||
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):
|
||||
@ -106,17 +113,18 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.23.1"
|
||||
version = "0.25.3"
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = {version = "0.17.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]
|
||||
clipboard-cli = ["clipboard"]
|
||||
default = ["shadow-rs"]
|
||||
clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
trash-support = ["trash"]
|
||||
|
4
crates/nu-cli/README.md
Normal file
4
crates/nu-cli/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# nu-cli
|
||||
|
||||
This crate provides the fundamental needs when creating the Nushell interactive REPL. In it, you'll find features for interacting with the line editor (the piece which writes the prompt and takes input from the user), keybindings, handlers for the commandline arguments passed to the REPL as it starts up, and more.
|
||||
|
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()
|
||||
}
|
525
crates/nu-cli/src/app.rs
Normal file
525
crates/nu-cli/src/app.rs
Normal file
@ -0,0 +1,525 @@
|
||||
mod logger;
|
||||
mod options;
|
||||
mod options_parser;
|
||||
|
||||
pub use options::{CliOptions, NuScript, Options};
|
||||
use options_parser::{NuParser, OptionsParser};
|
||||
|
||||
use nu_command::{commands::NuSignature as Nu, utils::test_bins as binaries};
|
||||
use nu_engine::{get_full_help, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Call, Expression, SpannedExpression, Synthetic};
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::{Span, Tag};
|
||||
use nu_stream::InputStream;
|
||||
|
||||
pub struct App {
|
||||
parser: Box<dyn OptionsParser>,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
|
||||
Self { parser, options }
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), ShellError> {
|
||||
let nu = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
let ui = App::new(nu, options);
|
||||
|
||||
ui.main(args)
|
||||
}
|
||||
|
||||
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
|
||||
let argv = quote_positionals(argv).join(" ");
|
||||
|
||||
if let Err(cause) = self.parse(&argv) {
|
||||
self.parser
|
||||
.context()
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(cause, &nu_source::Text::from(argv));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if self.help() {
|
||||
let context = self.parser.context();
|
||||
let stream = nu_stream::OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Nu, &context.scope))
|
||||
.into_value(nu_source::Tag::unknown()),
|
||||
);
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if self.version() {
|
||||
let context = self.parser.context();
|
||||
|
||||
let stream = nu_command::commands::version(nu_engine::CommandArgs {
|
||||
context: context.clone(),
|
||||
call_info: nu_engine::UnevaluatedCallInfo {
|
||||
args: Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("version".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
name_tag: Tag::unknown(),
|
||||
},
|
||||
input: InputStream::empty(),
|
||||
})?;
|
||||
|
||||
let stream = {
|
||||
let command = context
|
||||
.get_command("pivot")
|
||||
.expect("could not find version command");
|
||||
|
||||
context.run_command(
|
||||
command,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("pivot".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?
|
||||
};
|
||||
|
||||
consume(context, stream)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(bin) = self.testbin() {
|
||||
match bin.as_deref() {
|
||||
Ok("echo_env") => binaries::echo_env(),
|
||||
Ok("cococo") => binaries::cococo(),
|
||||
Ok("meow") => binaries::meow(),
|
||||
Ok("iecho") => binaries::iecho(),
|
||||
Ok("fail") => binaries::fail(),
|
||||
Ok("nonu") => binaries::nonu(),
|
||||
Ok("chop") => binaries::chop(),
|
||||
Ok("repeater") => binaries::repeater(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut opts = CliOptions::new();
|
||||
|
||||
opts.config = self.config().map(std::ffi::OsString::from);
|
||||
opts.stdin = self.takes_stdin();
|
||||
opts.save_history = self.save_history();
|
||||
|
||||
use logger::{configure, debug_filters, logger, trace_filters};
|
||||
|
||||
logger(|builder| {
|
||||
configure(&self, builder)?;
|
||||
trace_filters(&self, builder)?;
|
||||
debug_filters(&self, builder)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if let Some(commands) = self.commands() {
|
||||
let commands = commands?;
|
||||
let script = NuScript::code(&commands)?;
|
||||
opts.scripts = vec![script];
|
||||
let context = crate::create_default_context(false)?;
|
||||
return crate::run_script_file(context, opts);
|
||||
}
|
||||
|
||||
if let Some(scripts) = self.scripts() {
|
||||
let mut source_files = vec![];
|
||||
for script in scripts {
|
||||
let script_name = script?;
|
||||
let path = std::ffi::OsString::from(&script_name);
|
||||
|
||||
match NuScript::source_file(path.as_os_str()) {
|
||||
Ok(file) => source_files.push(file),
|
||||
Err(_) => {
|
||||
eprintln!("File not found: {}", script_name);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for file in source_files {
|
||||
let mut opts = opts.clone();
|
||||
opts.scripts = vec![file];
|
||||
|
||||
let context = crate::create_default_context(false)?;
|
||||
crate::run_script_file(context, opts)?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let context = crate::create_default_context(true)?;
|
||||
|
||||
if !self.skip_plugins() {
|
||||
let _ = crate::register_plugins(&context);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
{
|
||||
crate::cli(context, opts)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rustyline-support"))]
|
||||
{
|
||||
println!("Nushell needs the 'rustyline-support' feature for CLI support");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commands(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("commands").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn help(&self) -> bool {
|
||||
self.options
|
||||
.get("help")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> bool {
|
||||
self.options
|
||||
.get("version")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("args").map(|v| {
|
||||
v.table_entries()
|
||||
.map(|v| match &v.value {
|
||||
UntaggedValue::Error(err) => Err(err.clone()),
|
||||
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn takes_stdin(&self) -> bool {
|
||||
self.options
|
||||
.get("stdin")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Option<String> {
|
||||
self.options
|
||||
.get("config-file")
|
||||
.map(|v| v.as_string().expect("not a string"))
|
||||
}
|
||||
|
||||
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("develop").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("debug").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("loglevel").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("testbin").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn skip_plugins(&self) -> bool {
|
||||
self.options
|
||||
.get("skip-plugins")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn save_history(&self) -> bool {
|
||||
self.options
|
||||
.get("no-history")
|
||||
.map(|v| !matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
|
||||
self.parser.parse(&args).map(|options| {
|
||||
self.options.swap(&options);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn quote_positionals(parameters: &[String]) -> Vec<String> {
|
||||
parameters
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|arg| {
|
||||
if arg.contains(' ') {
|
||||
format!("\"{}\"", arg)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn consume(context: &EvaluationContext, stream: InputStream) -> Result<(), ShellError> {
|
||||
let autoview_cmd = context
|
||||
.get_command("autoview")
|
||||
.expect("could not find autoview command");
|
||||
|
||||
let stream = context.run_command(
|
||||
autoview_cmd,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?;
|
||||
|
||||
for _ in stream {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cli_app() -> App {
|
||||
let parser = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
|
||||
App::new(parser, options)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu")?;
|
||||
assert!(!ui.version());
|
||||
assert!(!ui.help());
|
||||
assert!(!ui.takes_stdin());
|
||||
assert!(ui.save_history());
|
||||
assert!(!ui.skip_plugins());
|
||||
assert_eq!(ui.config(), None);
|
||||
assert_eq!(ui.loglevel(), None);
|
||||
assert_eq!(ui.debug(), None);
|
||||
assert_eq!(ui.develop(), None);
|
||||
assert_eq!(ui.testbin(), None);
|
||||
assert_eq!(ui.commands(), None);
|
||||
assert_eq!(ui.scripts(), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
|
||||
assert!(ui.config().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --develop=cli,parser")?;
|
||||
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --debug=cli,run")?;
|
||||
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_loglevels() -> Result<(), ShellError> {
|
||||
for level in &["error", "warn", "info", "debug", "trace"] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --loglevel={}", *level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
|
||||
let ui = cli_app();
|
||||
let args = format!("nu -l {}", *level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --loglevel=nada")?;
|
||||
assert_eq!(
|
||||
ui.loglevel().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error("nada is not supported."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu code.nu bootstrap.nu")?;
|
||||
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
|
||||
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_test_binaries() -> Result<(), ShellError> {
|
||||
for binarie_name in &[
|
||||
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
|
||||
] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --testbin={}", *binarie_name);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --testbin=andres")?;
|
||||
assert_eq!(
|
||||
ui.testbin().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"andres is not supported."
|
||||
))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_version() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --version")?;
|
||||
assert!(ui.version());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_help() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --help")?;
|
||||
assert!(ui.help());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_take_stdin() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --stdin")?;
|
||||
assert!(ui.takes_stdin());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --no-history")?;
|
||||
assert!(!ui.save_history());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --skip-plugins")?;
|
||||
assert!(ui.skip_plugins());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"ls | get name\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
|
||||
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"echo 'hola'\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knows_custom_configurations() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --config-file /path/to/config.toml")?;
|
||||
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
52
crates/nu-cli/src/app/logger.rs
Normal file
52
crates/nu-cli/src/app/logger.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use super::App;
|
||||
use log::LevelFilter;
|
||||
use nu_errors::ShellError;
|
||||
use pretty_env_logger::env_logger::Builder;
|
||||
|
||||
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
|
||||
let mut builder = pretty_env_logger::formatted_builder();
|
||||
f(&mut builder)?;
|
||||
let _ = builder.try_init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(level) = app.loglevel() {
|
||||
let level = match level.as_deref() {
|
||||
Ok("error") => LevelFilter::Error,
|
||||
Ok("warn") => LevelFilter::Warn,
|
||||
Ok("info") => LevelFilter::Info,
|
||||
Ok("debug") => LevelFilter::Debug,
|
||||
Ok("trace") => LevelFilter::Trace,
|
||||
Ok(_) | Err(_) => LevelFilter::Warn,
|
||||
};
|
||||
|
||||
logger.filter_module("nu", level);
|
||||
};
|
||||
|
||||
if let Ok(s) = std::env::var("RUST_LOG") {
|
||||
logger.parse_filters(&s);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.develop() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Trace);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.debug() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Debug);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
100
crates/nu-cli/src/app/options.rs
Normal file
100
crates/nu-cli/src/app/options.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CliOptions {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
}
|
||||
|
||||
impl Default for CliOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
inner: RefCell<IndexMap<String, Value>>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(IndexMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
self.inner.borrow().get(key).map(Clone::clone)
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &str, value: Value) {
|
||||
self.inner.borrow_mut().insert(key.into(), value);
|
||||
}
|
||||
|
||||
pub fn shift(&self) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(ref mut args),
|
||||
..
|
||||
}) = self.inner.borrow_mut().get_mut("args")
|
||||
{
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap(&self, other: &Options) {
|
||||
self.inner.swap(&other.inner);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code(content: &str) -> Result<Self, ShellError> {
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: content.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
143
crates/nu-cli/src/app/options_parser.rs
Normal file
143
crates/nu-cli/src/app/options_parser.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use super::Options;
|
||||
|
||||
use nu_command::commands::{loglevels, testbins, NuSignature as Nu};
|
||||
use nu_command::commands::{Autoview, Pivot, Table, Version as NuVersion};
|
||||
use nu_engine::{whole_stream_command, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
pub struct NuParser {
|
||||
context: EvaluationContext,
|
||||
}
|
||||
|
||||
pub trait OptionsParser {
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError>;
|
||||
fn context(&self) -> &EvaluationContext;
|
||||
}
|
||||
|
||||
impl NuParser {
|
||||
pub fn new() -> Self {
|
||||
let context = EvaluationContext::basic();
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(Nu {}),
|
||||
whole_stream_command(NuVersion {}),
|
||||
whole_stream_command(Autoview {}),
|
||||
whole_stream_command(Pivot {}),
|
||||
whole_stream_command(Table {}),
|
||||
]);
|
||||
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsParser for NuParser {
|
||||
fn context(&self) -> &EvaluationContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError> {
|
||||
let options = Options::default();
|
||||
let (lite_result, _err) = nu_parser::lex(input, 0);
|
||||
let (lite_result, _err) = nu_parser::parse_block(lite_result);
|
||||
|
||||
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
|
||||
|
||||
if let Some(reason) = err {
|
||||
return Err(reason.into());
|
||||
}
|
||||
|
||||
match parsed.block[0].pipelines[0].list[0] {
|
||||
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
|
||||
if let Some(ref params) = args.named {
|
||||
params.iter().for_each(|(k, v)| {
|
||||
let value = match v {
|
||||
NamedValue::AbsentSwitch => {
|
||||
Some(UntaggedValue::from(false).into_untagged_value())
|
||||
}
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
|
||||
}
|
||||
NamedValue::AbsentValue => None,
|
||||
NamedValue::Value(span, exprs) => {
|
||||
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
|
||||
.expect("value");
|
||||
Some(value.value.into_value(Tag::from(span)))
|
||||
}
|
||||
};
|
||||
|
||||
let value =
|
||||
value
|
||||
.map(|v| match k.as_ref() {
|
||||
"testbin" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if testbins().iter().any(|n| name == *n) {
|
||||
Some(v)
|
||||
} else {
|
||||
Some(
|
||||
UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
),
|
||||
)
|
||||
.into_value(v.tag),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
"loglevel" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if loglevels().iter().any(|n| name == *n) {
|
||||
Some(v)
|
||||
} else {
|
||||
Some(
|
||||
UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
),
|
||||
)
|
||||
.into_value(v.tag),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
_ => Some(v),
|
||||
})
|
||||
.flatten();
|
||||
|
||||
if let Some(value) = value {
|
||||
options.put(&k, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut positional_args = vec![];
|
||||
|
||||
if let Some(positional) = &args.positional {
|
||||
for pos in positional {
|
||||
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
|
||||
positional_args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if !positional_args.is_empty() {
|
||||
options.put(
|
||||
"args",
|
||||
UntaggedValue::Table(positional_args).into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Error(ref reason) => {
|
||||
return Err(reason.clone().into());
|
||||
}
|
||||
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,250 +0,0 @@
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoenv;
|
||||
pub(crate) mod autoenv_trust;
|
||||
pub(crate) mod autoenv_untrust;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod benchmark;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
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 constants;
|
||||
pub(crate) mod count;
|
||||
pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod from;
|
||||
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_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 if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
pub(crate) mod math;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod move_;
|
||||
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 random;
|
||||
pub(crate) mod range;
|
||||
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 sort_by;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
pub(crate) mod str_;
|
||||
pub(crate) mod table;
|
||||
pub(crate) mod tags;
|
||||
pub(crate) mod to;
|
||||
pub(crate) mod to_csv;
|
||||
pub(crate) mod to_html;
|
||||
pub(crate) mod to_json;
|
||||
pub(crate) mod to_md;
|
||||
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 ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||
pub(crate) use benchmark::Benchmark;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::{
|
||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
};
|
||||
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 do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use if_::If;
|
||||
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;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
pub(crate) use from::From;
|
||||
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_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, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
|
||||
MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use move_::{Move, MoveColumn, Mv};
|
||||
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;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice};
|
||||
pub(crate) use range::Range;
|
||||
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, SkipUntil, SkipWhile};
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrFrom, StrLength, StrReverse,
|
||||
StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
pub(crate) use to::To;
|
||||
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_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,151 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
pub save: Option<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "the name of the alias")
|
||||
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run as the body of the alias",
|
||||
)
|
||||
.switch("save", "save the alias to your config", Some('s'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a shortcut for another command."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
alias(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "An alias without parameters",
|
||||
example: "alias say-hi [] { echo 'Hello!' }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "An alias with a single parameter",
|
||||
example: "alias l [x] { ls $x }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn alias(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let mut raw_input = args.raw_input.clone();
|
||||
let (
|
||||
AliasArgs {
|
||||
name,
|
||||
args: list,
|
||||
block,
|
||||
save,
|
||||
},
|
||||
_ctx,
|
||||
) = args.process(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input alias
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
if let Some(command) = commands.iter_mut().find(|command| {
|
||||
let cmd_str = command.as_string().unwrap_or_default();
|
||||
cmd_str.starts_with(&raw_input[..alias_start])
|
||||
}) {
|
||||
*command = alias;
|
||||
} else {
|
||||
commands.push(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let table = UntaggedValue::table(&[alias]);
|
||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||
}
|
||||
}
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(color.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown color",
|
||||
"unknown color",
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => {
|
||||
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
|
||||
}
|
||||
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => {
|
||||
Some(ansi_term::Color::Purple.underline().prefix().to_string())
|
||||
}
|
||||
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
}
|
||||
}
|
@ -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,92 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
pub struct Autoenv;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct Trusted {
|
||||
pub files: IndexMap<String, Vec<u8>>,
|
||||
}
|
||||
impl Trusted {
|
||||
pub fn new() -> Self {
|
||||
Trusted {
|
||||
files: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn file_is_trusted(nu_env_file: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
|
||||
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
|
||||
let nufile = std::fs::canonicalize(nu_env_file)?;
|
||||
|
||||
let trusted = read_trusted()?;
|
||||
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
|
||||
}
|
||||
|
||||
pub fn read_trusted() -> Result<Trusted, ShellError> {
|
||||
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(config_path)
|
||||
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
|
||||
let mut doc = String::new();
|
||||
file.read_to_string(&mut doc)?;
|
||||
|
||||
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||
Ok(allowed)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoenv {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv"
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
|
||||
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
|
||||
The file can contain several optional sections:
|
||||
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
|
||||
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
|
||||
scripts: scripts to run when entering the directory or leaving it. Note that exitscripts are not run in the directory they are declared in."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv")
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Example .nu-env file",
|
||||
example: r#"cat .nu-env
|
||||
[env]
|
||||
mykey = "myvalue"
|
||||
|
||||
[scriptvars]
|
||||
myscript = "echo myval"
|
||||
|
||||
[scripts]
|
||||
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||
exitscripts = ["touch bye.txt"]"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,349 +0,0 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
pub struct Autoview;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoview {
|
||||
fn name(&self) -> &str {
|
||||
"autoview"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoview")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View the contents of the pipeline as a table or list."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
autoview(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,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Automatically view the results",
|
||||
example: "ls | autoview",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Autoview is also implied. The above can be written as",
|
||||
example: "ls",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContextWithoutInput {
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
impl RunnableContextWithoutInput {
|
||||
pub fn convert(context: RunnableContext) -> (InputStream, RunnableContextWithoutInput) {
|
||||
let new_context = RunnableContextWithoutInput {
|
||||
shell_manager: context.shell_manager,
|
||||
host: context.host,
|
||||
ctrl_c: context.ctrl_c,
|
||||
current_errors: context.current_errors,
|
||||
registry: context.registry,
|
||||
name: context.name,
|
||||
};
|
||||
(context.input, new_context)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let binary = context.get_command("binaryview");
|
||||
let text = context.get_command("textview");
|
||||
let table = context.get_command("table");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
let pivot_mode = crate::data::config::config(Tag::unknown());
|
||||
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
|
||||
match v.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Always,
|
||||
}
|
||||
} else {
|
||||
AutoPivotMode::Always
|
||||
};
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
Some(y) => {
|
||||
let ctrl_c = context.ctrl_c.clone();
|
||||
let xy = vec![x, y];
|
||||
let xy_stream = futures::stream::iter(xy)
|
||||
.chain(input_stream)
|
||||
.interruptible(ctrl_c);
|
||||
|
||||
let stream = InputStream::from_stream(xy_stream);
|
||||
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match x {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(
|
||||
UntaggedValue::string(s).into_value(Tag { anchor, span }),
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(ref s)),
|
||||
tag: Tag { anchor, span },
|
||||
} if anchor.is_some() => {
|
||||
if let Some(text) = text {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(
|
||||
UntaggedValue::string(s).into_value(Tag { anchor, span }),
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}\n", s);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Path(s)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", s.display());
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", n);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Decimal(n)),
|
||||
..
|
||||
} => {
|
||||
// TODO: normalize decimal to remove trailing zeros.
|
||||
// normalization will be available in next release of bigdecimal crate
|
||||
let mut output = n.to_string();
|
||||
if output.contains('.') {
|
||||
output = output.trim_end_matches('0').to_owned();
|
||||
}
|
||||
if output.ends_with('.') {
|
||||
output.push('0');
|
||||
}
|
||||
out!("{}", output);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", b);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Duration(_)),
|
||||
..
|
||||
} => {
|
||||
let output = format_leaf(&x).plain_string(100_000);
|
||||
out!("{}", output);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(d)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", d);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(_)),
|
||||
..
|
||||
} => {
|
||||
let output = format_leaf(&x).plain_string(100_000);
|
||||
out!("{}", output);
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(ref b)),
|
||||
..
|
||||
} => {
|
||||
if let Some(binary) = binary {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
out!("{:?}", b.hex_dump());
|
||||
}
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
} => {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Row(row),
|
||||
..
|
||||
} if pivot_mode == AutoPivotMode::Always
|
||||
|| (pivot_mode == AutoPivotMode::Auto
|
||||
&& (row
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(_, v)| v.convert_to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.fold(0usize, |acc, len| acc + len.len())
|
||||
+ row.entries.iter().count() * 2)
|
||||
> term_width) =>
|
||||
{
|
||||
let mut entries = vec![];
|
||||
for (key, value) in row.entries.iter() {
|
||||
entries.push(vec![
|
||||
nu_table::StyledString::new(
|
||||
key.to_string(),
|
||||
nu_table::TextStyle {
|
||||
alignment: nu_table::Alignment::Left,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
),
|
||||
nu_table::StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
nu_table::TextStyle::basic(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
let table =
|
||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||
|
||||
nu_table::draw_table(&table, term_width);
|
||||
}
|
||||
|
||||
Value {
|
||||
value: ref item, ..
|
||||
} => {
|
||||
if let Some(table) = table {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs {
|
||||
let span = context.name.span;
|
||||
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: hir::Call {
|
||||
head: Box::new(SpannedExpression::new(
|
||||
Expression::Literal(Literal::String(String::new())),
|
||||
span,
|
||||
)),
|
||||
positional: None,
|
||||
named: None,
|
||||
span,
|
||||
is_last: true,
|
||||
},
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Autoview;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Autoview {})
|
||||
}
|
||||
}
|
@ -1,80 +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, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
pub struct Benchmark;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BenchmarkArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Benchmark {
|
||||
fn name(&self) -> &str {
|
||||
"benchmark"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("benchmark").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and benchmark",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
let start_time: chrono::DateTime<_> = Utc::now();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let output = match result {
|
||||
Ok(mut stream) => {
|
||||
let _ = stream.drain_vec().await;
|
||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
||||
context.clear_errors();
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
||||
tag: Tag::from(block.span),
|
||||
}))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
Ok(OutputStream::from(vec![output]))
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Char;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Char {
|
||||
fn name(&self) -> &str {
|
||||
"char"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output special characters (eg. 'newline')"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Output newline",
|
||||
example: r#"char newline"#,
|
||||
result: Some(vec![Value::from("\n")]),
|
||||
},
|
||||
Example {
|
||||
description: "Output prompt character, newline and a hamburger character",
|
||||
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("\u{25b6}").into(),
|
||||
UntaggedValue::string("\n").into(),
|
||||
UntaggedValue::string("\u{2261}").into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||
|
||||
let special_character = str_to_character(&name.item);
|
||||
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown character",
|
||||
"unknown character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
"sp" | "space" => Some(" ".into()),
|
||||
// Unicode names came from https://www.compart.com/en/unicode
|
||||
// Private Use Area (U+E000-U+F8FF)
|
||||
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||
|
||||
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
|
||||
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
|
||||
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
|
||||
|
||||
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
|
||||
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
|
||||
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
|
||||
|
||||
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
|
||||
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_string()), // ⨯
|
||||
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Char {})
|
||||
}
|
||||
}
|
@ -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,27 +0,0 @@
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
|
||||
use log::{log_enabled, trace};
|
||||
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
use nu_protocol::Value;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||
}
|
||||
|
||||
let registry = context.registry().clone();
|
||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
||||
|
||||
Ok(once(async { Ok(output) }).to_input_stream())
|
||||
}
|
@ -1,651 +0,0 @@
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::futures::ThreadedReceiver;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
|
||||
use futures::executor::block_on_stream;
|
||||
use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ExternalCommand;
|
||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
if !did_find_command(&command.name) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Command not found",
|
||||
"command not found",
|
||||
&command.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, scope, is_last).await
|
||||
}
|
||||
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let value =
|
||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
||||
.await?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
// what value we would put in its place.
|
||||
if value.value.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
|
||||
command_args.push(trimmed_value_string);
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let home_dir;
|
||||
|
||||
#[cfg(feature = "dirs")]
|
||||
{
|
||||
home_dir = dirs::home_dir;
|
||||
}
|
||||
#[cfg(not(feature = "dirs"))]
|
||||
{
|
||||
home_dir = || Some(std::path::PathBuf::from("/"));
|
||||
}
|
||||
|
||||
let arg = expand_tilde(arg.deref(), home_dir);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
||||
add_quotes(&arg)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(&command, &path, &process_args[..], input, is_last, scope)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
command: &ExternalCommand,
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
is_last: bool,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
let mut process = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut process = Command::new("cmd");
|
||||
process.arg("/c");
|
||||
process.arg(&command.name);
|
||||
for arg in args {
|
||||
// Clean the args before we use them:
|
||||
let arg = arg.replace("|", "\\|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
|
||||
let mut process = Command::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
};
|
||||
|
||||
process.current_dir(path);
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.env.iter());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
if !is_last {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
if !input.is_empty() {
|
||||
process.stdin(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||
}
|
||||
|
||||
trace!(target: "nu::run::external", "built command {:?}", process);
|
||||
|
||||
// TODO Switch to async_std::process once it's stabilized
|
||||
if let Ok(mut child) = process.spawn() {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
|
||||
let mut stdin = child.stdin.take();
|
||||
|
||||
let stdin_write_tx = tx.clone();
|
||||
let stdout_read_tx = tx;
|
||||
let stdin_name_tag = command.name_tag.clone();
|
||||
let stdout_name_tag = command.name_tag;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !input.is_empty() {
|
||||
let mut stdin_write = stdin
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
|
||||
for value in block_on_stream(input) {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if let Err(e) = stdin_write.write(b) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
"Received unexpected type from pipeline ({})",
|
||||
unsupported.type_name()
|
||||
),
|
||||
"expected a string",
|
||||
stdin_name_tag.clone(),
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !is_last {
|
||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||
stdout
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stdout for external command",
|
||||
"can't redirect stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stdout);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
|
||||
for line in block_on_stream(stream) {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s.clone())),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(b) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(
|
||||
b.into_iter().collect(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stderr);
|
||||
let err_stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
|
||||
for err_line in block_on_stream(err_stream) {
|
||||
match err_line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s.clone()),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
"Binary in stderr output",
|
||||
),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stderr ({})", e),
|
||||
"unable to read from stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
// than what other shells will do.
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"External command failed",
|
||||
"command failed",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::external_non_zero()),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ThreadedReceiver::new(rx);
|
||||
Ok(stream.to_input_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Failed to spawn process",
|
||||
"failed to spawn",
|
||||
&command.name_tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
|
||||
#[cfg(not(feature = "which"))]
|
||||
{
|
||||
// we can't perform this check, so just assume it can be found
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "which", unix))]
|
||||
{
|
||||
which::which(name).is_ok()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "which", windows))]
|
||||
{
|
||||
if which::which(name).is_ok() {
|
||||
true
|
||||
} else {
|
||||
let cmd_builtins = [
|
||||
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
|
||||
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
|
||||
"mklink",
|
||||
];
|
||||
|
||||
cmd_builtins.contains(&name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
|
||||
where
|
||||
SI: AsRef<str>,
|
||||
P: AsRef<std::path::Path>,
|
||||
HD: FnOnce() -> Option<P>,
|
||||
{
|
||||
shellexpand::tilde_with_context(input, home_dir)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
||||
argument.chars().any(|c| c.is_whitespace())
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
(argument.starts_with('"') && argument.ends_with('"'))
|
||||
|| (argument.starts_with('\'') && argument.ends_with('\''))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = argument.len();
|
||||
|
||||
Some(&argument[1..size - 1])
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
let mut original_paths = vec![];
|
||||
|
||||
if let Some(paths) = std::env::var_os("PATH") {
|
||||
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
original_paths
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, Context, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use futures::executor::block_on;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_errors::ShellError;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_protocol::Scope;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
|
||||
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next().await {
|
||||
// Ok(val) => {
|
||||
// if let Some(val) = val {
|
||||
// val.raw_value()
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// Err(_) => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
async fn non_existent_run() -> Result<(), ShellError> {
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, &Scope::new(), false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// async fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// .await?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
||||
// match read(stream.into()).await {
|
||||
// Some(Value {
|
||||
// value: UntaggedValue::Error(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
// None | _ => panic!("Command didn't fail."),
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn identifies_command_failed() -> Result<(), ShellError> {
|
||||
// block_on(failure_run())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
#[test]
|
||||
fn identifies_command_not_found() -> Result<(), ShellError> {
|
||||
block_on(non_existent_run())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(argument_is_quoted(""), false);
|
||||
|
||||
assert_eq!(argument_is_quoted("'"), false);
|
||||
assert_eq!(argument_is_quoted("'a"), false);
|
||||
assert_eq!(argument_is_quoted("a"), false);
|
||||
assert_eq!(argument_is_quoted("a'"), false);
|
||||
assert_eq!(argument_is_quoted("''"), true);
|
||||
|
||||
assert_eq!(argument_is_quoted(r#"""#), false);
|
||||
assert_eq!(argument_is_quoted(r#""a"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"a"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"a""#), false);
|
||||
assert_eq!(argument_is_quoted(r#""""#), true);
|
||||
|
||||
assert_eq!(argument_is_quoted("'andrés"), false);
|
||||
assert_eq!(argument_is_quoted("andrés'"), false);
|
||||
assert_eq!(argument_is_quoted(r#""andrés"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"andrés""#), false);
|
||||
assert_eq!(argument_is_quoted("'andrés'"), true);
|
||||
assert_eq!(argument_is_quoted(r#""andrés""#), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(remove_quotes(""), None);
|
||||
|
||||
assert_eq!(remove_quotes("'"), None);
|
||||
assert_eq!(remove_quotes("'a"), None);
|
||||
assert_eq!(remove_quotes("a"), None);
|
||||
assert_eq!(remove_quotes("a'"), None);
|
||||
assert_eq!(remove_quotes("''"), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes(r#"""#), None);
|
||||
assert_eq!(remove_quotes(r#""a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a""#), None);
|
||||
assert_eq!(remove_quotes(r#""""#), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes("'andrés"), None);
|
||||
assert_eq!(remove_quotes("andrés'"), None);
|
||||
assert_eq!(remove_quotes(r#""andrés"#), None);
|
||||
assert_eq!(remove_quotes(r#"andrés""#), None);
|
||||
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
||||
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expands_tilde_if_starts_with_tilde_character() {
|
||||
assert_eq!(
|
||||
expand_tilde("~", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
||||
"the_path_to_nu_light"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
|
||||
assert_eq!(
|
||||
expand_tilde("1~1", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
||||
"1~1"
|
||||
);
|
||||
}
|
||||
}
|
@ -1,257 +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);
|
||||
|
||||
if command.name == "autoenv untrust" {
|
||||
context.user_recently_used_autoenv_untrust = true;
|
||||
}
|
||||
|
||||
let result = {
|
||||
context
|
||||
.run_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&scope,
|
||||
objects,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let head = Arc::new(command.args.head.clone());
|
||||
//let context = Arc::new(context.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
let scope = Arc::new(scope);
|
||||
// let scope = scope.clone();
|
||||
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
.then(move |item| {
|
||||
let head = head.clone();
|
||||
let command = command.clone();
|
||||
let mut context = context.clone();
|
||||
let scope = scope.clone();
|
||||
async move {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
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: (&*head).clone(),
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
},
|
||||
};
|
||||
let result = converter
|
||||
.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
&context.registry,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(mut result) => {
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
|
||||
let mut output = vec![];
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
})) => {
|
||||
for l in list {
|
||||
output.push(Ok(l));
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
output
|
||||
.push(Ok(value
|
||||
.into_value(contents_tag.clone())));
|
||||
}
|
||||
Err(e) => output.push(Err(e)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output).to_input_stream()
|
||||
}
|
||||
Err(e) => {
|
||||
context.add_error(e);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
InputStream::one(tagged_contents)
|
||||
}
|
||||
}
|
||||
CommandAction::EnterHelpShell(value) => match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::index(&context.registry()) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match FilesystemShell::with_location(
|
||||
location,
|
||||
context.registry().clone(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err.into())
|
||||
.into_untagged_value(),
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
tag,
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
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());
|
||||
|
||||
InputStream::one(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.take_while(|x| futures::future::ready(!x.is_error())),
|
||||
))
|
||||
}
|
@ -1,109 +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, Value};
|
||||
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
|
||||
pub struct Clip;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Clip {
|
||||
fn name(&self) -> &str {
|
||||
"clip"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("clip")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
clip(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Save text to the clipboard",
|
||||
example: "echo 'secret value' | clip",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clip(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if let Ok(clip_context) = ClipboardProvider::new() {
|
||||
let mut clip_context: ClipboardContext = clip_context;
|
||||
let mut new_copy_data = String::new();
|
||||
|
||||
if !values.is_empty() {
|
||||
let mut first = true;
|
||||
for i in values.iter() {
|
||||
if !first {
|
||||
new_copy_data.push_str("\n");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
let string: String = match i.as_string() {
|
||||
Ok(string) => string.to_string(),
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Given non-string data",
|
||||
"expected strings from pipeline",
|
||||
name,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
new_copy_data.push_str(&string);
|
||||
}
|
||||
}
|
||||
|
||||
match clip_context.set_contents(new_copy_data) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not set contents of clipboard",
|
||||
"could not set contents of clipboard",
|
||||
name,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not open clipboard",
|
||||
"could not open clipboard",
|
||||
name,
|
||||
));
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clip;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clip {})
|
||||
}
|
||||
}
|
@ -1,440 +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 fn examples(&self) -> Vec<Example> {
|
||||
self.0.examples()
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||
))))
|
||||
} else {
|
||||
self.0.run(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
input,
|
||||
..
|
||||
}: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let func = self.func;
|
||||
|
||||
Ok(input
|
||||
.then(move |it| {
|
||||
let host = host.clone();
|
||||
let registry = registry.clone();
|
||||
let ctrl_c = ctrl_c.clone();
|
||||
let shell_manager = shell_manager.clone();
|
||||
let call_info = call_info.clone();
|
||||
async move {
|
||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(err));
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => return OutputStream::one(Err(err)),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Compact;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CompactArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Compact {
|
||||
fn name(&self) -> &str {
|
||||
"compact"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("compact").rest(SyntaxShape::Any, "the columns to compact from the table")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a table with non-empty rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
compact(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter out all null entries in a list",
|
||||
example: "echo [1 2 $null 3 $null $null] | compact",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -af | compact target",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compact(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (CompactArgs { rest: columns }, input) = args.process(®istry).await?;
|
||||
Ok(input
|
||||
.filter_map(move |item| {
|
||||
future::ready(if columns.is_empty() {
|
||||
if !item.is_empty() {
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
match item {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => {
|
||||
if columns
|
||||
.iter()
|
||||
.all(|field| r.get_data(field).borrow().is_some())
|
||||
{
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Compact;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Compact {})
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config clear"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config clear")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
clear(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Clear the config (be careful!)",
|
||||
example: "config clear",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
result.clear();
|
||||
|
||||
config::write(&result, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
|
||||
)))
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::{CommandArgs, CommandRegistry, OutputStream};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"config"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
get: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config get").required(
|
||||
"get",
|
||||
SyntaxShape::Any,
|
||||
"value to get from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
get(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the current startup commands",
|
||||
example: "config get startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (GetArgs { get }, _) = args.process(®istry).await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
.get(&key)
|
||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", get.tag()))?;
|
||||
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
let list: Vec<_> = list
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(x.clone()))
|
||||
.collect();
|
||||
|
||||
futures::stream::iter(list).to_output_stream()
|
||||
}
|
||||
x => {
|
||||
let x = x.clone();
|
||||
OutputStream::one(ReturnSuccess::value(x))
|
||||
}
|
||||
})
|
||||
}
|
@ -1,59 +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::path::PathBuf;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoadArgs {
|
||||
load: Tagged<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config load"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config load").required(
|
||||
"load",
|
||||
SyntaxShape::Path,
|
||||
"Path to load the config from",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Loads the config from the path given"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (LoadArgs { load }, _) = args.process(®istry).await?;
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
let result = crate::data::config::read(name_span, &Some(configuration))?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream())
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config path"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
path(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the path to the current config file",
|
||||
example: "config path",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn path(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let path = config::default_path()?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::Path(path)).into_value(args.call_info.name_tag),
|
||||
)))
|
||||
}
|
@ -1,75 +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;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveArgs {
|
||||
remove: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config remove"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config remove").required(
|
||||
"remove",
|
||||
SyntaxShape::Any,
|
||||
"remove a value from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
remove(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Remove the startup commands",
|
||||
example: "config --remove startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
if result.contains_key(&key) {
|
||||
result.swap_remove(&key);
|
||||
config::write(&result, &None)?;
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(remove.tag()),
|
||||
)])
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
"key",
|
||||
remove.tag(),
|
||||
))
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
key: Tagged<String>,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set")
|
||||
.required("key", SyntaxShape::String, "variable name to set")
|
||||
.required("value", SyntaxShape::Any, "value to use")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set completion_mode to circular",
|
||||
example: "config set [completion_mode circular]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (SetArgs { key, value }, _) = args.process(®istry).await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
||||
)))
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetIntoArgs {
|
||||
set_into: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set_into"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set_into").required(
|
||||
"set_into",
|
||||
SyntaxShape::String,
|
||||
"sets a variable from values in the pipeline",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set_into(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Store the contents of the pipeline as a path",
|
||||
example: "echo ['/usr/bin' '/bin'] | config set_into path",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_into(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (SetIntoArgs { set_into: v }, input) = args.process(®istry).await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
// In the original code, this is set to `Some` if the `--load flag is set`
|
||||
let configuration = None;
|
||||
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let key = v.to_string();
|
||||
|
||||
Ok(if rows.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No values given for set_into",
|
||||
"needs value(s) from pipeline",
|
||||
v.tag(),
|
||||
));
|
||||
} else if rows.len() == 1 {
|
||||
// A single value
|
||||
let value = &rows[0];
|
||||
|
||||
result.insert(key, value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
} else {
|
||||
// Take in the pipeline as a table
|
||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||
|
||||
result.insert(key, value);
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
})
|
||||
}
|
@ -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,175 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date")
|
||||
.switch("utc", "use universal time (UTC)", Some('u'))
|
||||
.switch("local", "use the local time", Some('l'))
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"report datetime in supplied strftime format",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the current datetime."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current local time and date",
|
||||
example: "date",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current UTC time and date",
|
||||
example: "date --utc",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it based on format",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it without a table",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
||||
|
||||
pub async fn date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let raw = args.has("raw");
|
||||
|
||||
let dt_fmt = if args.has("format") {
|
||||
if let Some(dt_fmt) = args.get("format") {
|
||||
dt_fmt.convert_to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let value = if args.has("utc") {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(utc, tag, dt_fmt)
|
||||
}
|
||||
} else {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Date {})
|
||||
}
|
||||
}
|
@ -1,111 +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};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DoArgs {
|
||||
block: Block,
|
||||
ignore_errors: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Do {
|
||||
fn name(&self) -> &str {
|
||||
"do"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("do")
|
||||
.required("block", SyntaxShape::Block, "the block to run ")
|
||||
.switch(
|
||||
"ignore_errors",
|
||||
"ignore errors as the block runs",
|
||||
Some('i'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block, optionally ignoring errors"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
do_(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run the block",
|
||||
example: r#"do { echo hello }"#,
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Run the block and ignore errors",
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: Some(vec![Value::nothing()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let is_last = raw_args.call_info.args.is_last;
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
ignore_errors,
|
||||
mut block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
block.set_is_last(is_last);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
if ignore_errors {
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec().await;
|
||||
context.clear_errors();
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Do;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Do {})
|
||||
}
|
||||
}
|
@ -1,171 +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, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Each;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachArgs {
|
||||
block: Block,
|
||||
numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Each {
|
||||
fn name(&self) -> &str {
|
||||
"each"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each")
|
||||
.required("block", SyntaxShape::Block, "the block to run on each row")
|
||||
.switch(
|
||||
"numbered",
|
||||
"returned a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
each(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Echo the sum of each row",
|
||||
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Echo the square of each integer",
|
||||
example: "echo [1 2 3] | each { echo $(= $it * $it) }",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
UntaggedValue::int(9).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Number each item and echo a message",
|
||||
example:
|
||||
"echo ['bob' 'fred'] | each --numbered { echo `{{$it.index}} is {{$it.item}}` }",
|
||||
result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||
matches!(&*head, SpannedExpression {
|
||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||
..
|
||||
} if s == "expanded-each")
|
||||
}
|
||||
|
||||
pub async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<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);
|
||||
|
||||
if each_args.numbered.item {
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(input.1.tag());
|
||||
dict.insert_untagged("index", UntaggedValue::int(input.0));
|
||||
dict.insert_value("item", input.1);
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, dict.into_value()).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Each;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Each {})
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{
|
||||
Primitive, Range, 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).await
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i.as_string() {
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||
))),
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag,
|
||||
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
struct RangeIterator {
|
||||
curr: Primitive,
|
||||
end: Primitive,
|
||||
tag: Tag,
|
||||
is_end_inclusive: bool,
|
||||
is_done: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||
RangeIterator {
|
||||
curr: range.from.0.item,
|
||||
end: range.to.0.item,
|
||||
tag,
|
||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||
is_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Result<ReturnSuccess, ShellError>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr != self.end {
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
|
||||
self.curr = match crate::data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
return Some(Err(ShellError::unimplemented(
|
||||
"Internal error: expected a primitive result from increment",
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
return Some(Err(ShellError::coerce_error(
|
||||
left_type.spanned(self.tag.span),
|
||||
right_type.spanned(self.tag.span),
|
||||
)));
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
} else if self.is_end_inclusive && !self.is_done {
|
||||
self.is_done = true;
|
||||
Some(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
|
||||
))
|
||||
} else {
|
||||
// TODO: add inclusive/exclusive ranges
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Echo {})
|
||||
}
|
||||
}
|
@ -1,200 +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>,
|
||||
encoding: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[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",
|
||||
)
|
||||
.named(
|
||||
"encoding",
|
||||
SyntaxShape::String,
|
||||
"encoding to use to open file",
|
||||
Some('e'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
r#"Create a new shell and begin at this path.
|
||||
|
||||
Multiple encodings are supported for reading text files by using
|
||||
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||
|
||||
For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
enter(args, registry).await
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
Example {
|
||||
description: "Enters file with iso-8859-1 encoding",
|
||||
example: "enter file.csv --encoding iso-8859-1",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn enter(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let 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, encoding }, _) = 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) {
|
||||
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
|
||||
)))
|
||||
} else if location.is_dir() {
|
||||
Ok(OutputStream::one(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, tagged_contents) = crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
tag.span,
|
||||
encoding,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tagged_contents.value {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
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 tag = tagged_contents.tag.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;
|
||||
Ok(futures::stream::iter(result_vec.into_iter().map(
|
||||
move |res| match res {
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
||||
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
||||
value,
|
||||
tag: tag.clone(),
|
||||
})),
|
||||
),
|
||||
x => x,
|
||||
},
|
||||
))
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Enter;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Enter {})
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use crate::commands::command::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||
|
||||
pub struct Exit;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exit {
|
||||
fn name(&self) -> &str {
|
||||
"exit"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("exit").switch("now", "exit out of the shell immediately", Some('n'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Exit the current shell (or all shells)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exit(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Exit the current shell",
|
||||
example: "exit",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Exit all shells (exiting Nu)",
|
||||
example: "exit --now",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exit(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
|
||||
let command_action = if args.call_info.args.has("now") {
|
||||
CommandAction::Exit
|
||||
} else {
|
||||
CommandAction::LeaveShell
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(command_action)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Exit;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Exit {})
|
||||
}
|
||||
}
|
@ -1,163 +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::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
pub struct Format;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
pattern: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Format {
|
||||
fn name(&self) -> &str {
|
||||
"format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format").required(
|
||||
"pattern",
|
||||
SyntaxShape::String,
|
||||
"the pattern to output. Eg) \"{foo}: {bar}\"",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
format_command(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Print filenames with their sizes",
|
||||
example: "ls | format '{name}: {size}'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn format_command(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = Arc::new(format_pattern);
|
||||
|
||||
Ok(input
|
||||
.then(move |value| {
|
||||
let mut output = String::new();
|
||||
let commands = commands.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
|
||||
async move {
|
||||
for command in &*commands {
|
||||
match command {
|
||||
FormatCommand::Text(s) => {
|
||||
output.push_str(&s);
|
||||
}
|
||||
FormatCommand::Column(c) => {
|
||||
// FIXME: use the correct spans
|
||||
let full_column_path = nu_parser::parse_full_column_path(
|
||||
&(c.to_string()).spanned(Span::unknown()),
|
||||
&*registry,
|
||||
);
|
||||
|
||||
let result = evaluate_baseline_expr(
|
||||
&full_column_path.0,
|
||||
®istry,
|
||||
&value,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(c) = result {
|
||||
output
|
||||
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
|
||||
} else {
|
||||
// That column doesn't match, so don't emit anything
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FormatCommand {
|
||||
Text(String),
|
||||
Column(String),
|
||||
}
|
||||
|
||||
fn format(input: &str) -> Vec<FormatCommand> {
|
||||
let mut output = vec![];
|
||||
|
||||
let mut loop_input = input.chars();
|
||||
loop {
|
||||
let mut before = String::new();
|
||||
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '{' {
|
||||
break;
|
||||
}
|
||||
before.push(c);
|
||||
}
|
||||
|
||||
if !before.is_empty() {
|
||||
output.push(FormatCommand::Text(before.to_string()));
|
||||
}
|
||||
// Look for column as we're now at one
|
||||
let mut column = String::new();
|
||||
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '}' {
|
||||
break;
|
||||
}
|
||||
column.push(c);
|
||||
}
|
||||
|
||||
if !column.is_empty() {
|
||||
output.push(FormatCommand::Column(column.to_string()));
|
||||
}
|
||||
|
||||
if before.is_empty() && column.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Format;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Format {})
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct From;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for From {
|
||||
fn name(&self) -> &str {
|
||||
"from"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::From;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(From {})
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use crate::commands::from_delimited_data::from_delimited_data;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct FromTSV;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FromTSVArgs {
|
||||
headerless: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FromTSV {
|
||||
fn name(&self) -> &str {
|
||||
"from tsv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("from tsv").switch(
|
||||
"headerless",
|
||||
"don't treat the first row as column names",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse text as .tsv and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_tsv(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_tsv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (FromTSVArgs { headerless }, input) = args.process(®istry).await?;
|
||||
|
||||
from_delimited_data(headerless, '\t', "TSV", input, name).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTSV;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTSV {})
|
||||
}
|
||||
}
|
@ -1,263 +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).await
|
||||
}
|
||||
|
||||
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 async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
}
|
||||
}
|
@ -1,374 +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;
|
||||
use nu_value_ext::as_string;
|
||||
|
||||
pub struct GroupBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByArgs {
|
||||
grouper: Option<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupBy {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group-by").optional(
|
||||
"grouper",
|
||||
SyntaxShape::Any,
|
||||
"the grouper value to use",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"create a new table grouped."
|
||||
}
|
||||
|
||||
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 column named \"type\"",
|
||||
example: r#"ls | group-by type"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "blocks can be used for generating a grouping key (same as above)",
|
||||
example: r#"ls | group-by { get type }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "you can also group by raw values by leaving out the argument",
|
||||
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()]),
|
||||
},
|
||||
Example {
|
||||
description: "write pipelines for a more involved grouping key",
|
||||
example:
|
||||
"echo [1 3 1 3 2 1 1] | group-by { echo `({{$it}} - 1) % 3` | calc | str from }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
enum Grouper {
|
||||
ByColumn(Option<Tagged<String>>),
|
||||
ByBlock,
|
||||
}
|
||||
|
||||
pub async fn group_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(args.call_info.args.head.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&args, ®istry));
|
||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let mut keys: Vec<Result<String, ShellError>> = vec![];
|
||||
let mut group_strategy = Grouper::ByColumn(None);
|
||||
|
||||
match grouper {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Block(block_given),
|
||||
..
|
||||
}) => {
|
||||
let block = Arc::new(block_given);
|
||||
let error_key = "error";
|
||||
|
||||
for value in values.iter() {
|
||||
let run = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
match crate::commands::each::process_row(run, scope, head, context, value.clone())
|
||||
.await
|
||||
{
|
||||
Ok(mut s) => {
|
||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
s.drain_vec().await;
|
||||
|
||||
if collection.len() > 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected one value from the block",
|
||||
"requires a table with one value for grouping",
|
||||
&name,
|
||||
));
|
||||
}
|
||||
|
||||
let value = match collection.get(0) {
|
||||
Some(Ok(return_value)) => {
|
||||
return_value.raw_value().unwrap_or_else(|| {
|
||||
UntaggedValue::string(error_key).into_value(&name)
|
||||
})
|
||||
}
|
||||
Some(Err(_)) | None => {
|
||||
UntaggedValue::string(error_key).into_value(&name)
|
||||
}
|
||||
};
|
||||
|
||||
keys.push(as_string(&value));
|
||||
}
|
||||
Err(_) => {
|
||||
keys.push(Ok(error_key.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group_strategy = Grouper::ByBlock;
|
||||
}
|
||||
Some(other) => {
|
||||
group_strategy = Grouper::ByColumn(Some(as_string(&other)?.tagged(&name)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if values.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected table from pipeline",
|
||||
"requires a table input",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
match group_strategy {
|
||||
Grouper::ByBlock => {
|
||||
let map = keys.clone();
|
||||
|
||||
let block = Box::new(move |idx: usize, row: &Value| match map.get(idx) {
|
||||
Some(Ok(key)) => Ok(key.clone()),
|
||||
Some(Err(reason)) => Err(reason.clone()),
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
match crate::utils::data::group(&values, &Some(block), &name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
||||
Grouper::ByColumn(column_name) => match group(&column_name, &values, name) {
|
||||
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
|
||||
Err(reason) => Err(reason),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(
|
||||
column_name: &Option<Tagged<String>>,
|
||||
values: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let name = tag.into();
|
||||
|
||||
let grouper = if let Some(column_name) = column_name {
|
||||
Grouper::ByColumn(Some(column_name.clone()))
|
||||
} else {
|
||||
Grouper::ByColumn(None)
|
||||
};
|
||||
|
||||
match grouper {
|
||||
Grouper::ByColumn(Some(column_name)) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
match row.get_data_by_key(column_name.borrow_spanned()) {
|
||||
Some(group_key) => Ok(as_string(&group_key)?),
|
||||
None => Err(suggestions(column_name.borrow_tagged(), &row)),
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| match as_string(row) {
|
||||
Ok(group_key) => Ok(group_key),
|
||||
Err(reason) => Err(reason),
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByBlock => Err(ShellError::unimplemented(
|
||||
"Block not implemented: This should never happen.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::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_committers() -> Vec<Value> {
|
||||
vec![
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("Sept 24-2019")},
|
||||
),
|
||||
row(
|
||||
indexmap! {"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||
let for_key = Some(String::from("date").tagged_unknown());
|
||||
let sample = table(&nu_releases_committers());
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, &sample, Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"August 23-2019".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("JT"), "country".into() => string("NZ"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("YK"), "country".into() => string("US"), "date".into() => string("August 23-2019")})
|
||||
]),
|
||||
"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 = Some(String::from("country").tagged_unknown());
|
||||
let sample = table(&nu_releases_committers());
|
||||
|
||||
assert_eq!(
|
||||
group(&for_key, &sample, Tag::unknown())?,
|
||||
row(indexmap! {
|
||||
"EC".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("Sept 24-2019")}),
|
||||
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("October 10-2019")})
|
||||
]),
|
||||
"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,169 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{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).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (HelpArgs { rest }, ..) = args.process(®istry).await?;
|
||||
|
||||
if !rest.is_empty() {
|
||||
if rest[0].item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
Ok(
|
||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
return None;
|
||||
}
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
.as_string()
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
);
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||
®istry,
|
||||
))))
|
||||
} 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) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
} else if let Some(command) = registry.get_command(&rest[0].item) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
rest[0].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/"#;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(msg).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
|
||||
get_documentation(cmd, registry, &DocumentationConfig::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Help;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Help {})
|
||||
}
|
||||
}
|
@ -1,266 +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).await
|
||||
}
|
||||
|
||||
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 async fn histogram(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (HistogramArgs { column_name, rest }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let groups = group(&Some(column_name.clone()), &values, &name)?;
|
||||
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
|
||||
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
|
||||
let evaled = evaluate(&sorted, None, &name)?;
|
||||
let reduced = reduce(&evaled, None, &name)?;
|
||||
let maxima = map_max(&reduced, None, &name)?;
|
||||
let percents = percentages(&reduced, maxima, &name)?;
|
||||
|
||||
match percents {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let mut idx = 0;
|
||||
|
||||
let column_names_supplied: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
};
|
||||
|
||||
let column = (*column_name).clone();
|
||||
|
||||
let count_column_name = "count".to_string();
|
||||
let count_shell_error = ShellError::labeled_error(
|
||||
"Unable to load group count",
|
||||
"unabled to load group count",
|
||||
&name,
|
||||
);
|
||||
let mut count_values: Vec<u64> = Vec::new();
|
||||
|
||||
for table_entry in reduced.table_entries() {
|
||||
match table_entry {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
for i in list {
|
||||
if let Ok(count) = i.value.clone().into_value(&name).as_u64() {
|
||||
count_values.push(count);
|
||||
} else {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(count_shell_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(start),
|
||||
..
|
||||
} = datasets.get(0).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load dataset",
|
||||
"unabled to load dataset",
|
||||
&name,
|
||||
)
|
||||
})? {
|
||||
let start = start.clone();
|
||||
Ok(
|
||||
futures::stream::iter(start.into_iter().map(move |percentage| {
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let value: Tagged<String> = group_labels
|
||||
.get(idx)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load group labels",
|
||||
"unabled to load group labels",
|
||||
&name,
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
fact.insert_value(
|
||||
&column,
|
||||
UntaggedValue::string(value.item).into_value(value.tag),
|
||||
);
|
||||
|
||||
fact.insert_untagged(
|
||||
&count_column_name,
|
||||
UntaggedValue::int(count_values[idx]),
|
||||
);
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(ref num)),
|
||||
ref tag,
|
||||
} = percentage
|
||||
{
|
||||
let string = std::iter::repeat("*")
|
||||
.take(num.to_i32().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a number",
|
||||
"expected a number",
|
||||
tag,
|
||||
)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
fact.insert_untagged(
|
||||
&frequency_column_name,
|
||||
UntaggedValue::string(string),
|
||||
);
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
}
|
||||
_ => Ok(OutputStream::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
fn percentages(values: &Value, max: Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let results: Value = match values {
|
||||
Value {
|
||||
value: UntaggedValue::Table(datasets),
|
||||
..
|
||||
} => {
|
||||
let datasets: Vec<_> = datasets
|
||||
.iter()
|
||||
.map(|subsets| match subsets {
|
||||
Value {
|
||||
value: UntaggedValue::Table(data),
|
||||
..
|
||||
} => {
|
||||
let data = data
|
||||
.iter()
|
||||
.map(|d| match d {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(n)),
|
||||
..
|
||||
} => {
|
||||
let max = match &max {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(maxima)),
|
||||
..
|
||||
} => maxima.clone(),
|
||||
_ => Zero::zero(),
|
||||
};
|
||||
|
||||
let n = (n * 100) / max;
|
||||
|
||||
UntaggedValue::int(n).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::int(0).into_value(&tag),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
UntaggedValue::Table(data).into_value(&tag)
|
||||
}
|
||||
_ => UntaggedValue::Table(vec![]).into_value(&tag),
|
||||
})
|
||||
.collect();
|
||||
|
||||
UntaggedValue::Table(datasets).into_value(&tag)
|
||||
}
|
||||
other => other.clone(),
|
||||
};
|
||||
|
||||
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,67 +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 history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
Ok(line) => Some(ReturnSuccess::value(
|
||||
UntaggedValue::string(line).into_value(tag.clone()),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not open history",
|
||||
"history file could not be opened",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct If;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IfArgs {
|
||||
condition: Block,
|
||||
then_case: Block,
|
||||
else_case: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for If {
|
||||
fn name(&self) -> &str {
|
||||
"if"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("if")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must match",
|
||||
)
|
||||
.required(
|
||||
"then_case",
|
||||
SyntaxShape::Block,
|
||||
"block to run if condition is true",
|
||||
)
|
||||
.required(
|
||||
"else_case",
|
||||
SyntaxShape::Block,
|
||||
"block to run if condition is false",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run blocks if a condition is true or false."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if_command(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run a block if a condition is true",
|
||||
example: "echo 10 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
|
||||
result: Some(vec![UntaggedValue::string("greater than 5").into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Run a block if a condition is false",
|
||||
example: "echo 1 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
|
||||
result: Some(vec![UntaggedValue::string("less than or equal to 5").into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
async fn if_command(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
|
||||
let (
|
||||
IfArgs {
|
||||
condition,
|
||||
then_case,
|
||||
else_case,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
let condition = {
|
||||
if condition.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match condition.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let condition = condition.clone();
|
||||
let then_case = then_case.clone();
|
||||
let else_case = else_case.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let mut context = context.clone();
|
||||
|
||||
async move {
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition =
|
||||
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
|
||||
.await;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => {
|
||||
if b {
|
||||
match run_block(
|
||||
&then_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => stream.to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
|
||||
.to_output_stream(),
|
||||
}
|
||||
} else {
|
||||
match run_block(
|
||||
&else_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => stream.to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
|
||||
.to_output_stream(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream()
|
||||
}
|
||||
},
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::If;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(If {})
|
||||
}
|
||||
}
|
@ -1,82 +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).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (InsertArgs { column, value }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |row| match row {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => Ok(ReturnSuccess::Value(
|
||||
row.insert_data_at_column_path(&column, value.clone())?,
|
||||
)),
|
||||
|
||||
Value { tag, .. } => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
)),
|
||||
})
|
||||
.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).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if rest.len() <= 2 {
|
||||
let field = rest.get(0);
|
||||
let replacement_if_true = rest.get(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Some(field), Some(replacement_if_true)) => {
|
||||
IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
)
|
||||
}
|
||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
)),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val = crate::commands::get::get_column_path(&field, &out)?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
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?;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(out))
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IsEmpty;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IsEmpty {})
|
||||
}
|
||||
}
|
@ -1,120 +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, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
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 = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(call_info
|
||||
.input
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
!matches!(result, Ok(ref v) if v.is_true())
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,120 +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, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
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 = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let call_info = args.evaluate_once(®istry).await?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = Arc::new(match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(call_info
|
||||
.input
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let item = item.clone();
|
||||
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
let result = evaluate_baseline_expr(
|
||||
&*condition,
|
||||
®istry,
|
||||
&item,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
matches!(result, Ok(ref v) if v.is_true())
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! command {
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($number:tt)* }
|
||||
Rest {}
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$(
|
||||
($named_param:tt : $named_type:ty : $named_kind:tt)
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$( ( $param_name:tt : $param_type:tt ) )*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct $export;
|
||||
|
||||
impl Command for $export {
|
||||
fn run(&self, $args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
fn command($args: EvaluatedCommandArgs, ( $($param_name),*, ): ( $($param_type),*, )) -> Result<OutputStream, ShellError> {
|
||||
let output = $body;
|
||||
|
||||
Ok(output.boxed().to_output_stream())
|
||||
}
|
||||
|
||||
let $args = $args.evaluate_once(registry)?;
|
||||
let tuple = ( $($extract ,)* );
|
||||
command( $args, tuple )
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
stringify!($config_name)
|
||||
}
|
||||
|
||||
fn config(&self) -> $nu_parser::registry::Signature {
|
||||
$nu_parser::registry::Signature {
|
||||
name: self.name().to_string(),
|
||||
positional: vec![$($mandatory_positional)*],
|
||||
rest_positional: false,
|
||||
is_filter: false,
|
||||
is_sink: false,
|
||||
|
||||
named: {
|
||||
use $nu_parser::registry::NamedType;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut named: indexmap::IndexMap<String, NamedType> = indexmap::IndexMap::new();
|
||||
|
||||
$(
|
||||
named.insert(stringify!($named_param).to_string(), $nu_parser::registry::NamedType::$named_kind);
|
||||
)*
|
||||
|
||||
named
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// switch
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident : Switch , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Switch : Switch)
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : Switch)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// mandatory named arguments
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Mandatory(NamedValue::Single))
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// optional named arguments
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident ? : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Optional(NamedValue::Single))
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// mandatory positional block
|
||||
(
|
||||
Named { $export:ident $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { $param_name:ident : Block , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* $nu_parser::registry::PositionalType::mandatory_block(
|
||||
stringify!($param_name)
|
||||
), ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : Block)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
Block::extract(value)?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// mandatory positional argument
|
||||
(
|
||||
Named { $export:ident $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { $param_name:ident : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* $nu_parser::registry::PositionalType::mandatory(
|
||||
stringify!($param_name), <$param_kind>::syntax_type()
|
||||
), ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
<$param_kind>::extract(&value)?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
($export:ident as $config_name:tt ( $args:ident , $($command_rest:tt)* ) $body:block) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { 0 }
|
||||
Rest { $($command_rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
rest_positional: false,
|
||||
named: {}
|
||||
}
|
||||
|
||||
Function {
|
||||
}
|
||||
|
||||
Extract {
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use bigdecimal::{FromPrimitive, Zero};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{convert_number_to_u64, Number, Operator},
|
||||
Primitive, Signature, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math avg"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math avg")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the average of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
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,
|
||||
},
|
||||
average,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the average of a list of numbers",
|
||||
example: "echo [-50 100.0 25] | math avg",
|
||||
result: Some(vec![UntaggedValue::decimal(25).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let sum = reducer_for(Reduce::Summation);
|
||||
|
||||
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to big decimal",
|
||||
"could not convert to big decimal",
|
||||
&name.span,
|
||||
)
|
||||
})?;
|
||||
|
||||
let total_rows = UntaggedValue::decimal(number);
|
||||
let total = sum(Value::zero(), values.to_vec())?;
|
||||
|
||||
match total {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Filesize(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::filesize(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::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubCommandArgs {
|
||||
expression: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math eval"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Evaluate a math expression into a number"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math eval").desc(self.usage()).optional(
|
||||
"math expression",
|
||||
SyntaxShape::String,
|
||||
"the math expression to evaluate",
|
||||
)
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
eval(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Evalulate math in the pipeline",
|
||||
example: "echo '10 / 4' | math eval",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn eval(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.span;
|
||||
let (SubCommandArgs { expression }, input) = args.process(registry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |x| {
|
||||
if let Some(Tagged {
|
||||
tag,
|
||||
item: expression,
|
||||
}) = &expression
|
||||
{
|
||||
UntaggedValue::string(expression).into_value(tag)
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
.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(
|
||||
"Math evaluation 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<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
|
||||
match meval::eval_str(math_expression) {
|
||||
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
||||
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
|
||||
Err(error) => Err(error.to_string().to_lowercase()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
use super::variance::variance;
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math stddev"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math stddev")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the stddev of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
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,
|
||||
},
|
||||
stddev,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the stddev of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math stddev",
|
||||
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stddev(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let variance = variance(values, name)?.as_primitive()?;
|
||||
let sqrt_var = match variance {
|
||||
Primitive::Decimal(var) => var.sqrt(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not take square root of variance",
|
||||
"error occured here",
|
||||
name.span,
|
||||
))
|
||||
}
|
||||
};
|
||||
match sqrt_var {
|
||||
Some(stddev) => Ok(UntaggedValue::from(Primitive::Decimal(stddev)).into_value(name)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Could not calculate stddev",
|
||||
"error occured here",
|
||||
name.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::data_processing::{reducer_for, Reduce};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
|
||||
use num_traits::identities::Zero;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math sum"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math sum")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the sum of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
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,
|
||||
},
|
||||
summation,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sum a list of numbers",
|
||||
example: "echo [1 2 3] | math sum",
|
||||
result: Some(vec![UntaggedValue::int(6).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the disk usage for the current directory",
|
||||
example: "ls --all --du | get size | math sum",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let sum = reducer_for(Reduce::Summation);
|
||||
|
||||
if values.iter().all(|v| v.is_primitive()) {
|
||||
Ok(sum(Value::zero(), values.to_vec())?)
|
||||
} else {
|
||||
let mut column_values = IndexMap::new();
|
||||
|
||||
for value in values {
|
||||
if let UntaggedValue::Row(row_dict) = value.value.clone() {
|
||||
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()]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut column_totals = IndexMap::new();
|
||||
|
||||
for (col_name, col_vals) in column_values {
|
||||
let sum = sum(Value::zero(), col_vals)?;
|
||||
|
||||
column_totals.insert(col_name, sum);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::Row(Dictionary {
|
||||
entries: column_totals,
|
||||
})
|
||||
.into_value(name))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
pub type MathFunction = fn(values: &[Value], tag: &Tag) -> Result<Value, ShellError>;
|
||||
|
||||
pub async fn run_with_function(
|
||||
RunnableContext {
|
||||
mut input, name, ..
|
||||
}: RunnableContext,
|
||||
mf: MathFunction,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let values: Vec<Value> = input.drain_vec().await;
|
||||
let res = calculate(&values, &name, mf);
|
||||
match res {
|
||||
Ok(v) => {
|
||||
if v.value.is_table() {
|
||||
Ok(OutputStream::from(
|
||||
v.table_entries()
|
||||
.map(|v| ReturnSuccess::value(v.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(v)))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
|
||||
if values.iter().all(|v| v.is_primitive()) {
|
||||
mf(&values, &name)
|
||||
} else {
|
||||
// If we are not dealing with Primitives, then perhaps we are dealing with a table
|
||||
// Create a key for each column name
|
||||
let mut column_values = IndexMap::new();
|
||||
for value in values {
|
||||
if let UntaggedValue::Row(row_dict) = &value.value {
|
||||
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()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The mathematical function operates over the columns of the table
|
||||
let mut column_totals = IndexMap::new();
|
||||
for (col_name, col_vals) in column_values {
|
||||
column_totals.insert(col_name, mf(&col_vals, &name)?);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::Row(Dictionary {
|
||||
entries: column_totals,
|
||||
})
|
||||
.into_untagged_value())
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::compute_values;
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::{FromPrimitive, Zero};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Operator, Primitive, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math variance"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math variance")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the variance of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
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,
|
||||
},
|
||||
variance,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance",
|
||||
result: Some(vec![UntaggedValue::decimal(2).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn sum_of_squares(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to big decimal",
|
||||
"could not convert to big decimal",
|
||||
&name.span,
|
||||
)
|
||||
})?;
|
||||
let mut sum_x = Value::zero();
|
||||
let mut sum_x2 = Value::zero();
|
||||
for value in values {
|
||||
let v = match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
|
||||
..
|
||||
} => {
|
||||
UntaggedValue::from(Primitive::Int(num.clone().into()))
|
||||
},
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(num),
|
||||
..
|
||||
} => {
|
||||
UntaggedValue::from(num.clone())
|
||||
},
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Attempted to compute the sum of squared values of a value that cannot be summed or squared.",
|
||||
"value appears here",
|
||||
value.tag.span,
|
||||
))
|
||||
}
|
||||
};
|
||||
let v_squared = compute_values(Operator::Multiply, &v, &v);
|
||||
match v_squared {
|
||||
// X^2
|
||||
Ok(x2) => sum_x2 = sum_x2 + x2.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned(value.tag.span),
|
||||
right_type.spanned(value.tag.span),
|
||||
))
|
||||
}
|
||||
};
|
||||
sum_x = sum_x + v.into_untagged_value();
|
||||
}
|
||||
|
||||
let sum_x_squared = match compute_values(Operator::Multiply, &sum_x, &sum_x) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
))
|
||||
}
|
||||
};
|
||||
let sum_x_squared_div_n = match compute_values(Operator::Divide, &sum_x_squared, &n.into()) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
))
|
||||
}
|
||||
};
|
||||
let ss = match compute_values(Operator::Minus, &sum_x2, &sum_x_squared_div_n) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ss)
|
||||
}
|
||||
|
||||
pub fn variance(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let ss = sum_of_squares(values, name)?;
|
||||
let n = BigDecimal::from_usize(values.len()).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to big decimal",
|
||||
"could not convert to big decimal",
|
||||
&name.span,
|
||||
)
|
||||
})?;
|
||||
let variance = compute_values(Operator::Divide, &ss, &n.into());
|
||||
match variance {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
Err((_, _)) => Err(ShellError::labeled_error(
|
||||
"could not calculate variance of non-integer or unrelated types",
|
||||
"source",
|
||||
name,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"move"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("move")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"moves across desired subcommand."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
mod column;
|
||||
mod command;
|
||||
pub mod mv;
|
||||
|
||||
pub use column::SubCommand as MoveColumn;
|
||||
pub use command::Command as Move;
|
||||
pub use mv::Mv;
|
@ -1,106 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NthArgs {
|
||||
row_number: Tagged<u64>,
|
||||
rest: Vec<Tagged<u64>>,
|
||||
}
|
||||
|
||||
pub struct Nth;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Nth {
|
||||
fn name(&self) -> &str {
|
||||
"nth"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nth")
|
||||
.required(
|
||||
"row number",
|
||||
SyntaxShape::Int,
|
||||
"the number of the row to return",
|
||||
)
|
||||
.rest(SyntaxShape::Any, "Optionally return more rows")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return only the selected rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
nth(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the second row",
|
||||
example: "echo [first second third] | nth 1",
|
||||
result: Some(vec![Value::from("second")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the first and third rows",
|
||||
example: "echo [first second third] | nth 0 2",
|
||||
result: Some(vec![Value::from("first"), Value::from("third")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
NthArgs {
|
||||
row_number,
|
||||
rest: and_rows,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
|
||||
let row_numbers = vec![vec![row_number], and_rows]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|x| x.item)
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
let max_row_number = row_numbers
|
||||
.iter()
|
||||
.max()
|
||||
.expect("Internal error: should be > 0 row numbers");
|
||||
|
||||
Ok(input
|
||||
.take(*max_row_number as usize + 1)
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, item)| {
|
||||
futures::future::ready(
|
||||
if row_numbers.iter().any(|requested| *requested == idx as u64) {
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Nth;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Nth {})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user