mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
987 Commits
Author | SHA1 | Date | |
---|---|---|---|
9a56665c6b | |||
8044fb2db0 | |||
3a59ab9f14 | |||
f609a4f26a | |||
80463d12fb | |||
cef05d3553 | |||
5879b0df99 | |||
95cd9dd2b2 | |||
424d5611a5 | |||
9bff68a4f6 | |||
a9bdc655c1 | |||
9b617de6f0 | |||
771270d526 | |||
26d1307476 | |||
86707b9972 | |||
52cb865c5c | |||
3ea027a136 | |||
00469de93e | |||
9bc4e6794d | |||
429127793f | |||
75cb3fcc5f | |||
f0e87da830 | |||
c5639cd9fa | |||
95d4922e44 | |||
bdd52f0111 | |||
7bd07cb351 | |||
249afc5df4 | |||
d7af461173 | |||
b17e9f4ed0 | |||
6862734580 | |||
9e1f645428 | |||
c4818d79f3 | |||
d1a78a58cd | |||
65d0b5b9d9 | |||
614bc2a943 | |||
27b06358ea | |||
e56c01d0e2 | |||
ececca7ad2 | |||
e9cc417fd5 | |||
81a7d17b33 | |||
9382dd6d55 | |||
7aa2a57434 | |||
9b88ea5b60 | |||
8bfcea8054 | |||
f3d2be7a56 | |||
be31182969 | |||
b543063749 | |||
ce0060e6b0 | |||
35b12fe5ec | |||
6ac26094da | |||
8c6a0f68d4 | |||
f5d6672ccf | |||
db06edc5d3 | |||
568927349d | |||
4f812a7f34 | |||
38fc42d352 | |||
b4c5693ac6 | |||
79000aa5e0 | |||
2415381682 | |||
b499e7c682 | |||
d8cde2ae89 | |||
ddc00014be | |||
9ffa3e55c2 | |||
45fe3be83e | |||
dd6fe6a04a | |||
e76b38882c | |||
11bdab7e61 | |||
3d682fe957 | |||
a43e66ef92 | |||
3be7996e79 | |||
5041a4ffa3 | |||
b16b3c0b7f | |||
852ec3f9a0 | |||
dd7b7311b3 | |||
9364bad625 | |||
6fc5244439 | |||
8e1112c1dd | |||
9d1cb1bfaf | |||
216d7d035f | |||
ead6fbdf9c | |||
5b616770df | |||
23a5c5dc09 | |||
74656bf976 | |||
d8a2e0e9a3 | |||
046e46b962 | |||
ec08e4bc6d | |||
05e07ddf5c | |||
757d7479af | |||
440feaf74a | |||
22c50185b5 | |||
3a2c7900d6 | |||
fa8629300f | |||
37dc226996 | |||
4e1f94026c | |||
d27263af97 | |||
215f1af1da | |||
1291b647ae | |||
91df6c236f | |||
58cea7e8b4 | |||
b01f50bd70 | |||
5f48452e3b | |||
dae1b9a996 | |||
a21af0ade4 | |||
28123841ba | |||
183be911d0 | |||
1966809502 | |||
c3c41a61b0 | |||
90849a067f | |||
705f12c1d9 | |||
0826e66fe0 | |||
774769a7ad | |||
e72cecf457 | |||
9c1a3aa244 | |||
2d07c6eedb | |||
fdd92b2dda | |||
8c70189422 | |||
075c83b3a1 | |||
d3a19c5ac7 | |||
080874df10 | |||
24848a1e35 | |||
e215fbbd08 | |||
33aea56ccd | |||
b6683a3010 | |||
578ef04988 | |||
735a7a21bd | |||
80a69224f7 | |||
e0bf17930b | |||
db3177a5aa | |||
98b9839e3d | |||
0db4d89838 | |||
52278f8562 | |||
c19d9597fd | |||
d9d9916ccc | |||
26759c4af2 | |||
e529746294 | |||
0c4d4632ef | |||
e2c1216c1b | |||
d83dbc3670 | |||
0242b30027 | |||
0c656fd276 | |||
b7a3e5989d | |||
5b5f1d1b92 | |||
35bea5e044 | |||
7917cf9f00 | |||
5036672a58 | |||
585ab56ea4 | |||
9009f68e09 | |||
2bacc29d30 | |||
4d7d97e0e6 | |||
f1000a17b4 | |||
f43edbccdc | |||
6b4282eadf | |||
7e2781a2af | |||
32a53450a6 | |||
ce78817f41 | |||
f0e93c2fa9 | |||
fa6bb147ea | |||
220b105efb | |||
b56ad92e25 | |||
fc5fe4b445 | |||
c01d44e37d | |||
5a0e86aa70 | |||
b4529a20e8 | |||
b938adefaa | |||
b39d797c1f | |||
4240bfb7b1 | |||
b7572f107f | |||
379e3d70ca | |||
6fc87fad72 | |||
fa15a2856a | |||
eaec480f42 | |||
d18587330a | |||
5114dfca7d | |||
3395beaa56 | |||
df66d9fcdf | |||
1af1e0b5a3 | |||
9b41f9ecb8 | |||
48ade4993d | |||
4ecc807dbb | |||
86b69cc5d1 | |||
017a13fa3f | |||
ca12b2e30e | |||
db6c804b17 | |||
57ff668d2e | |||
9fb9b16b38 | |||
41178dff90 | |||
12deff5d1b | |||
21a645b1a9 | |||
e8a55aa647 | |||
6295b20545 | |||
850ecf648a | |||
e6cf18ea43 | |||
d28624796c | |||
380c216d77 | |||
ee5a387300 | |||
3ac36879e0 | |||
bc0c9ab698 | |||
5762489070 | |||
fcdc474731 | |||
f491d3e1e1 | |||
94c89eb623 | |||
cf0a18be51 | |||
0621ab6652 | |||
64a028cc76 | |||
f71a45235a | |||
718ee3d545 | |||
e92678ea2c | |||
1f175d4c98 | |||
4d6ccf2540 | |||
aa6c3936d2 | |||
4f05994b36 | |||
b27d6b2cb1 | |||
64f226f7da | |||
5c1606ed82 | |||
11977759ce | |||
bc3dc98b34 | |||
6fadc72553 | |||
a9e6b1ec6b | |||
cbc7b94b02 | |||
45c66e2090 | |||
11b2423544 | |||
1f9907d2ff | |||
fd503fceaf | |||
b7e5790cd1 | |||
3caab5de36 | |||
fa97e819eb | |||
bdc4bf97a7 | |||
cfb0f3961b | |||
8fa965118c | |||
9c800bcb2c | |||
ea4d8c5f49 | |||
5d2abdd1c3 | |||
7d5333db3b | |||
2a8a628b72 | |||
c4d2b787aa | |||
a4e11726cf | |||
9850fbd77d | |||
2ccb91dc6a | |||
3e76ed9122 | |||
2223fd663a | |||
3f960012cd | |||
0b094e2bf2 | |||
2388e1e80b | |||
62e34b69b3 | |||
fd68767216 | |||
93202d4529 | |||
ed1f0eb231 | |||
04612809ab | |||
8cca447e8c | |||
651e86a3c0 | |||
c3c3481ef5 | |||
0732d8bbba | |||
bdca31cc2d | |||
ed7aea8dd3 | |||
e813e44501 | |||
f46c45343a | |||
b12ffb8888 | |||
cf96677c78 | |||
ce03d8eb12 | |||
21dedef7f6 | |||
da7f77867a | |||
3415594877 | |||
0c38729735 | |||
a0b3a48e8b | |||
b662c2eb96 | |||
8cda641350 | |||
efdfeac55e | |||
e0577e15f2 | |||
bb0b0870ea | |||
74a73f9838 | |||
c9f9078726 | |||
eb875ea949 | |||
b4a0e4c0dc | |||
88a0705df1 | |||
7bcd96fc65 | |||
833825ae9a | |||
899383c30c | |||
d9d6cea5a9 | |||
d01ccd5a54 | |||
a896892ac9 | |||
d89d1894d0 | |||
587536ddcc | |||
ced5e1065f | |||
7479173811 | |||
4b83a2d27a | |||
41f72b1236 | |||
c98a6705e6 | |||
6454bf69aa | |||
bd30ea723e | |||
2dd4cb9f7d | |||
1784b4bf50 | |||
8e4b85e29b | |||
7098e56ccf | |||
02ad491dea | |||
708fee535c | |||
7636cc7fe4 | |||
f856e64fb3 | |||
a783a084d4 | |||
81b12d02ec | |||
336df6c65e | |||
35f9299fc6 | |||
649c8319e6 | |||
99cf5871aa | |||
da04e9d801 | |||
2db2d98ef0 | |||
817eacccd8 | |||
ce6d3c6eb2 | |||
ef32e1ce1a | |||
c1105e945e | |||
69b089845c | |||
75556f6c5f | |||
b650d1ef79 | |||
099b571e8f | |||
cb926f7b49 | |||
13515c5eb0 | |||
58d960d914 | |||
4a83bb6c93 | |||
3e56e81d06 | |||
312e9bf5d6 | |||
18d7e64660 | |||
f1118020a1 | |||
63433f1bc8 | |||
921a66554e | |||
bb0d08a721 | |||
fe14e52e77 | |||
24d72ca43c | |||
457f7889df | |||
7b0c0692dc | |||
c4cb3a77cb | |||
aed8d3800b | |||
53a9264b67 | |||
e18fb13616 | |||
2201bd9b09 | |||
da8f6c5682 | |||
14d7ba5cc9 | |||
5ee096232c | |||
c259ef41bd | |||
2c238aea6a | |||
c600c1ebe7 | |||
df94052180 | |||
f878276de7 | |||
cd89304706 | |||
2b9f258126 | |||
85587c0c2a | |||
6cc4ef6c70 | |||
517173bb8c | |||
e415be6c0e | |||
59332562bb | |||
3d8d7787de | |||
611fe41788 | |||
a6118eed8d | |||
d4798d6ee1 | |||
5ea245badf | |||
5ee7847035 | |||
8a812cf03c | |||
9a1cedfd08 | |||
766d1ef374 | |||
beec658872 | |||
b90d701f89 | |||
be5d71ea47 | |||
f1bde69131 | |||
36ae384fb3 | |||
2c4048eb43 | |||
b9195c2668 | |||
bb968304da | |||
ca9bf19041 | |||
ecfee4c542 | |||
acb34561eb | |||
43aec8cdbe | |||
e46d610f77 | |||
1d95861a09 | |||
f48de73236 | |||
0b4daa66b0 | |||
412952182f | |||
014d36b17a | |||
f44f3a8af1 | |||
457514590d | |||
843d8c2242 | |||
ce4ae00a6f | |||
4f7f6a2932 | |||
7039602e4d | |||
acb7aff6fb | |||
8940ee6c3f | |||
3b26b4355e | |||
b56e603c58 | |||
66c2a36123 | |||
8838815737 | |||
834522d002 | |||
f281cd5aa3 | |||
5add5cbd12 | |||
902aad6016 | |||
13f87857cf | |||
e0cc2c9112 | |||
92ab8b831b | |||
6a7a60429f | |||
79fd7d54b2 | |||
ebca840d91 | |||
17b2bcc125 | |||
24a98f8999 | |||
e49b359848 | |||
c9fb381d69 | |||
8224ec49bc | |||
fe7e87ee02 | |||
a3dce8ff19 | |||
89f3cbf318 | |||
3f555a6836 | |||
4fdf5c663c | |||
1ec41a0ab4 | |||
ab0a6b6ca6 | |||
e3bf6fdfc0 | |||
46c0d29c08 | |||
76ccd5668a | |||
c6436eb32f | |||
b2c29117d9 | |||
ffb1dfb012 | |||
88c6fa9933 | |||
60df45a390 | |||
10aa86272b | |||
d37e6ba3b5 | |||
5eee33c7e4 | |||
5e748ae8fc | |||
50e53e788a | |||
7336e1df1a | |||
a724a8fe7d | |||
c731a4e275 | |||
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba | |||
e76b3d61de | |||
1adebefc3e | |||
d1e1d0ac3e | |||
b398448cd9 | |||
02f92fa527 | |||
773d167449 | |||
aa92141ad7 | |||
80624267fd | |||
2030e25ddc | |||
247fff424d | |||
c902d8bc0c | |||
b0e5723a68 | |||
9273bb3f72 | |||
f7d3ccfc70 | |||
d86350af80 | |||
14512988ba | |||
33e1120add | |||
3278d290be | |||
daec3fc3d3 | |||
a6ba58ec41 | |||
65327e0e7e | |||
3ed3712fdc | |||
f46962d236 | |||
aa4778ff07 | |||
e81689f2c0 | |||
4656310a1c | |||
34e58bc5d6 | |||
fbe9d6f529 | |||
e266590813 | |||
b27148d14b | |||
4858a9a817 | |||
3ec53e544c | |||
c52d45cb97 | |||
11531b7630 | |||
a098a27837 | |||
2591bd8c63 | |||
a03fb946d9 | |||
9c58f2a522 | |||
3cb9147f22 | |||
f1d72e2670 | |||
f1e7a01b2e | |||
b88ace4cde | |||
34d7c17e78 | |||
3f1824111d | |||
fbae137442 | |||
9850424251 | |||
918ec9daa8 | |||
7b502a4c7f | |||
7b07e976b8 | |||
e45b169cba | |||
5ebfa10495 | |||
3f93dc2f1d | |||
a43514deb2 | |||
ab77bf3289 | |||
0afe1e4e67 | |||
ef26d539a7 | |||
fce8581321 | |||
ba6abd77c9 | |||
a7295c8f1b | |||
2a310ef187 | |||
530e250573 | |||
6fbc76bc0f | |||
884382bac4 | |||
d97975e9fa | |||
839b264261 | |||
7ef4e5f940 | |||
646aace05b | |||
772ad896c8 | |||
9c4bbe3c63 | |||
c5ca839294 | |||
5337a6dffa | |||
56ce10347e | |||
37bc90c62a | |||
ad7522bba0 | |||
bbcf374886 | |||
99c42582fe | |||
5a56d47f25 | |||
529c98085a | |||
2b955f82b7 | |||
1843fdc060 | |||
ec4e3a6d5c | |||
4ab468e65f | |||
1d18f6947e | |||
df3b6d9d26 | |||
4bbdb73668 | |||
62d3497bbb | |||
e614970c08 | |||
d931331b57 | |||
f18da2609a | |||
2ef9cc118e | |||
33674d3a98 | |||
0167649e6f | |||
cc263ee15d | |||
a4809f2e68 | |||
21770367e2 | |||
6145f734b7 | |||
9d8d305e9d | |||
eb55fd2383 | |||
613d2fb8df | |||
8783742060 | |||
20528e96c7 | |||
3b6c4c1bb5 | |||
cb18dd5200 | |||
ccebdd7a7f | |||
c3efb12733 | |||
d885258dc7 | |||
2da915d0c7 | |||
ae64c58f59 | |||
ff6868b329 | |||
47ef193600 | |||
c2f4969d4f | |||
08c98967e0 | |||
8d091f6f83 | |||
58094987ff | |||
ce26ef97e4 | |||
8f9bd4a299 | |||
45dd7d8770 | |||
0f10d984c3 | |||
2e5d981a09 | |||
c74254c2cb | |||
271fda7c91 | |||
0e5886ace1 | |||
4b89c5f900 | |||
dcab255d59 | |||
fc8512be39 | |||
e10ef4aaae | |||
0b70ca8451 | |||
555d9ee763 | |||
121b801baa | |||
9adcecbbf1 | |||
9f131d998d | |||
cd0a04f02a | |||
aaf5684f9c | |||
2f0cb044a5 | |||
8b55757a0b | |||
84fae6e07e | |||
63e220a763 | |||
a96fc21f88 | |||
1ba5b25b29 | |||
a871f2344a | |||
a217bc0715 | |||
34ab4d8360 | |||
48f1c3a49e | |||
692376e830 | |||
cc99df5ef1 | |||
d255a2a050 | |||
c07835f3ad | |||
78a5067434 | |||
cdeb8de75d | |||
606547ecb4 | |||
3b809b38e8 | |||
7c49a42b68 | |||
87823b0cb5 | |||
ebf845f431 | |||
ce6df93d05 | |||
e7958bebac | |||
233afebdf0 | |||
56069af42d | |||
376d22e331 | |||
7fc8ff60fd | |||
1f4791a191 | |||
2ac7a4d48d | |||
01386f4d58 | |||
1086fbe9b5 | |||
a83bd4ab20 | |||
26caf7e1b2 | |||
dd2a0e35f4 | |||
6a4eabf5c7 | |||
0e2c888f73 | |||
c140da5740 | |||
586c0ea3d8 | |||
d6f4189c7b | |||
7a820b1304 | |||
767201c40d | |||
3c3614a120 | |||
9e24e452a5 | |||
2cffff0c1b | |||
cf2e9cf481 | |||
6b2c7a4c86 | |||
98e199f7b5 | |||
10e463180e | |||
c9d0003818 | |||
e2a21afca8 | |||
2ea209bcc0 | |||
e049ca8ebf | |||
9037a9467b | |||
4c6cf36aa5 | |||
c92211c016 | |||
8bd6b5b913 | |||
b67fe31544 | |||
c8adb06ca7 | |||
9695331eed | |||
d42cfab6ef | |||
2b7c811402 | |||
c6cb491e77 | |||
e2a4632159 | |||
65f0edd14b | |||
b2c466bca6 | |||
6b4e577032 | |||
b12a3dd0e5 | |||
d856ac92f4 | |||
f5856b0914 | |||
8c675a0d31 | |||
86a0e77065 | |||
72c27bd095 | |||
e4e27b6e11 | |||
475d32045f | |||
3643ee6dfd | |||
32e4535f24 | |||
daa2148136 | |||
9097e865ca | |||
894d3e7452 | |||
5a5c65ee4b | |||
8b35239bce | |||
87e2fa137a | |||
46f64c6fdc | |||
10536f70f3 | |||
0812a08bfb | |||
5706eddee3 | |||
0b429fde24 | |||
388ff78a26 | |||
7d46177cf3 | |||
8a0bd20e84 | |||
a1a5a3646b | |||
453c11b4b5 | |||
c66b97126f | |||
0646f1118c | |||
0bcfa12e0d | |||
b2ec32fdf0 | |||
8f00848ff9 | |||
604025fe34 | |||
98126e2981 | |||
db9b88089e | |||
a35a71fd82 | |||
558cd58d09 | |||
410f3ef0f0 | |||
ae765c71fd | |||
e5684bc34c | |||
b4a7e7e6e9 | |||
41669e60c8 | |||
eeaca50dee | |||
d8d88cd395 | |||
9aabafeb41 | |||
9ced5915ff | |||
9d0be7d96f | |||
57a6465ba0 | |||
5cc6505512 | |||
3d45f77692 | |||
e01974b7ab | |||
1f01677b7b | |||
58ee2bf06a | |||
7bf09559a6 | |||
8dea08929a | |||
26f31da711 | |||
d95a065e3d | |||
ed50210832 | |||
ceafe434b5 | |||
89b374cb16 | |||
47c1f475bf | |||
61e027b227 | |||
58ab5aa887 | |||
2b2117173c | |||
f2a79cf381 | |||
ad9449bf00 | |||
c2f8f4bd9b | |||
8b6232ac87 | |||
93a965e3e2 | |||
217c2bae99 | |||
b9bbf0c10f | |||
a54f9719e5 | |||
a5470b2362 | |||
c1bf9fd897 | |||
f3036b8cfd | |||
9b6b817276 | |||
9e3c64aa84 | |||
920e0acb85 | |||
b7d3623e53 | |||
3676a8a48d | |||
f85a1d003c | |||
121e8678b6 | |||
e4c512e33d | |||
81df42d63b | |||
6802a4ee21 | |||
c0ce78f892 | |||
221f36ca65 | |||
125e60d06a | |||
83458510a9 | |||
eac5f62959 | |||
b19cc799aa | |||
efa56d0147 | |||
47f6d20131 | |||
e0b4ab09eb | |||
8abf28093a | |||
d1687df067 | |||
e77219a59f | |||
22edb37162 | |||
1ac87715ff | |||
de162c9aea | |||
390d06d4e7 | |||
89acbda877 | |||
0d40d0438f | |||
1e8212a938 | |||
2da8310b11 | |||
c16d8f0d5f | |||
2ac5b0480a | |||
4e90b478b7 | |||
3a38fb94f0 | |||
c6f6dcb57c | |||
b80299eba7 | |||
a48616697a | |||
b82dccf0bd | |||
84caf8859f | |||
be7f35246e | |||
3917fda7ed | |||
3b357e5402 | |||
79da470239 | |||
37949e70e0 | |||
5d00ecef56 | |||
6dde231dde | |||
58fa2e51a2 | |||
cf0877bf72 | |||
a0db4ce747 | |||
6ee13126f7 | |||
1c15a4ed3a | |||
7aabc381a3 | |||
8c9dced71b | |||
06d5a31301 | |||
ffbc0b0180 | |||
c0901ef707 | |||
d3e84daa49 | |||
228ede18cf | |||
c5a69271a2 | |||
dc9d939c83 | |||
32f0f94b46 | |||
a142d1a192 | |||
173d60d59d | |||
f2989bf704 | |||
575ddbd4ef | |||
ef9b72d360 | |||
25349a1eac | |||
99e4c44862 | |||
1345f97202 | |||
f02076daa8 | |||
533e04a60a | |||
13c152b00f | |||
f231a6df4a | |||
3c0bccb900 | |||
f43a65d7a7 | |||
0827ed143d | |||
4b84825dbf | |||
82ae06865c | |||
128ce6f9b7 | |||
44cbd88b55 | |||
7164929c61 | |||
848ff8453b | |||
f94ca6cfde | |||
fab3f8fd40 | |||
dbcfcdae89 | |||
08aa248c42 | |||
9f07bcc66f | |||
2caa44cea8 | |||
28c21121cf | |||
a17d46f200 | |||
6cc8402127 | |||
5f0ad1d6ad | |||
8d7bb9147e | |||
bc48b4553c | |||
28c07a5072 | |||
30c8dabeb4 | |||
8b368b6a4e | |||
8c0d60d0fb | |||
8b0a4ccf4c | |||
cfe4eff566 | |||
38f3957edf | |||
cb66d2bcad | |||
ff73623873 | |||
d1c719a8cc | |||
4d854f36af | |||
8d5848c955 | |||
fe88d58b1e | |||
42dbfd1fa0 | |||
534e1fc3ce | |||
ff946a2f21 | |||
3c0cbec993 | |||
48e29e9ed6 | |||
ff53352afe | |||
4fd4136d50 | |||
dc1248a454 | |||
de554f8e5f | |||
44979f3051 | |||
7ae7394c85 | |||
9dbf7556b8 | |||
caafd26deb | |||
43a218240c | |||
11d7d8ea1e | |||
2dea9e6f1f | |||
c5cb369d8d | |||
b6959197bf | |||
d5b99ae316 | |||
9d10007085 | |||
2e0b964d5b | |||
5bae7e56ef | |||
b42ef45c7c | |||
3423cd54a1 | |||
837f0463eb | |||
56f6f683fc | |||
c57f41e5f2 | |||
8c74b1e437 | |||
8318d59ef1 | |||
64efa30f3e | |||
820a6bfb08 | |||
b8d253cbd7 | |||
3c421c5726 | |||
75b2d26187 | |||
17a5aa3052 | |||
e4a22799d5 | |||
fda456e469 | |||
e5d38dcff6 | |||
a82fa75c31 | |||
0c16464320 | |||
888758b813 | |||
cb909f810e | |||
a75318d7e8 | |||
7a9bf06005 | |||
a06299c77a | |||
e4bcd1934d | |||
4673adecc5 | |||
1b8051ece5 | |||
d44059c36b | |||
b79abdb2a5 | |||
ee8a0c9477 | |||
41853b9f18 | |||
997d56a288 | |||
0769e9b750 | |||
f5519e2a09 | |||
8259d463aa | |||
e2c015f725 | |||
eb12fffbc6 | |||
c42096c34e | |||
46eb34b35d | |||
23a73cd31f | |||
6c07bc10e2 | |||
6365ba0286 | |||
545b1dcd94 | |||
fb89f2f48c | |||
f6ee21f76b | |||
d69a4db2e7 | |||
d4bfbb5eaf | |||
507f24d029 | |||
230c36f2fb | |||
219c719e98 | |||
50146bdef3 | |||
2042f7f769 | |||
0594f9e7aa | |||
3b8deb9ec7 | |||
727ff5f2d4 | |||
3d62528d8c | |||
a42d419b66 | |||
9602e82029 | |||
8e98df8b28 | |||
2daf8ec72d | |||
afcacda35f |
@ -1,6 +1,7 @@
|
|||||||
# increase the default windows stack size
|
|
||||||
[target.x86_64-pc-windows-msvc]
|
[target.x86_64-pc-windows-msvc]
|
||||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
# increase the default windows stack size
|
||||||
|
# statically link the CRT so users don't have to install it
|
||||||
|
rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
# keeping this but commentting out in case we need them in the future
|
# keeping this but commentting out in case we need them in the future
|
||||||
|
|
||||||
@ -11,3 +12,17 @@ rustflags = ["-C", "link-args=-stack:10000000"]
|
|||||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||||
# [target.x86_64-apple-darwin]
|
# [target.x86_64-apple-darwin]
|
||||||
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
||||||
|
|
||||||
|
# How to use mold in linux and mac
|
||||||
|
|
||||||
|
# [target.x86_64-unknown-linux-gnu]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"]
|
||||||
|
|
||||||
|
# [target.x86_64-apple-darwin]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
|
# [target.aarch64-apple-darwin]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
||||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: context
|
id: context
|
||||||
attributes:
|
attributes:
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: "When you want a new feature for something that doesn't already exist"
|
description: "When you want a new feature for something that doesn't already exist"
|
||||||
|
labels: "enhancement"
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
|
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Question
|
||||||
|
description: "When you have a question to ask"
|
||||||
|
labels: "question"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Question
|
||||||
|
description: Leave your question here
|
||||||
|
placeholder: |
|
||||||
|
A clear and concise question
|
||||||
|
Example: Is there any equivalent of bash's $CDPATH in Nu?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context and details
|
||||||
|
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
|
||||||
|
validations:
|
||||||
|
required: false
|
23
.github/pull_request_template.md
vendored
23
.github/pull_request_template.md
vendored
@ -1,11 +1,24 @@
|
|||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
(description of your pull request here)
|
_(Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.)_
|
||||||
|
|
||||||
# Tests
|
_(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_
|
||||||
|
|
||||||
|
# User-Facing Changes
|
||||||
|
|
||||||
|
_(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_
|
||||||
|
|
||||||
|
# Tests + Formatting
|
||||||
|
|
||||||
|
Don't forget to add tests that cover your changes.
|
||||||
|
|
||||||
Make sure you've run and fixed any issues with these commands:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
- `cargo test --workspace` to check that all tests pass
|
||||||
|
|
||||||
|
# After Submitting
|
||||||
|
|
||||||
|
If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
|
||||||
|
129
.github/workflows/ci.yml
vendored
129
.github/workflows/ci.yml
vendored
@ -11,40 +11,42 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
||||||
|
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
||||||
|
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||||
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
|
style: [default, dataframe]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
|
include:
|
||||||
|
- style: default
|
||||||
|
flags: ""
|
||||||
|
- style: dataframe
|
||||||
|
flags: "--features=dataframe "
|
||||||
|
exclude:
|
||||||
|
# only test dataframes on Ubuntu (the fastest platform)
|
||||||
|
- platform: windows-latest
|
||||||
|
style: dataframe
|
||||||
|
- platform: macos-latest
|
||||||
|
style: dataframe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
NUSHELL_CARGO_TARGET: ci
|
NUSHELL_CARGO_TARGET: ci
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
- name: cargo fmt
|
||||||
with:
|
run: cargo fmt --all -- --check
|
||||||
key: "v2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Rustfmt
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: fmt
|
|
||||||
args: --all -- --check
|
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
|
||||||
|
|
||||||
nu-tests:
|
nu-tests:
|
||||||
env:
|
env:
|
||||||
@ -53,42 +55,32 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
style: [extra, default]
|
style: [default, dataframe]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
include:
|
include:
|
||||||
- style: extra
|
|
||||||
flags: "--features=extra"
|
|
||||||
- style: default
|
- style: default
|
||||||
flags: ""
|
flags: ""
|
||||||
|
- style: dataframe
|
||||||
|
flags: "--features=dataframe"
|
||||||
exclude:
|
exclude:
|
||||||
|
# only test dataframes on Ubuntu (the fastest platform)
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
style: default
|
style: dataframe
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
style: default
|
style: dataframe
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
|
||||||
|
|
||||||
python-virtualenv:
|
python-virtualenv:
|
||||||
env:
|
env:
|
||||||
@ -97,7 +89,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-20.04, macos-latest, windows-latest]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
py:
|
py:
|
||||||
@ -106,37 +98,24 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: "2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo install --locked --path=. --profile ci --no-default-features
|
||||||
with:
|
|
||||||
command: install
|
|
||||||
args: --path=. --profile ci --no-default-features
|
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
- run: python -m pip install tox
|
- run: python -m pip install tox
|
||||||
|
|
||||||
|
# Get only the latest tagged version for stability reasons
|
||||||
- name: Install virtualenv
|
- name: Install virtualenv
|
||||||
run: |
|
run: git clone https://github.com/pypa/virtualenv.git && cd virtualenv && git checkout $(git describe --tags | cut -d - -f 1)
|
||||||
git clone https://github.com/kubouch/virtualenv.git && \
|
|
||||||
cd virtualenv && \
|
|
||||||
git checkout engine-q-update
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
@ -152,30 +131,20 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo test --profile ci --package nu_plugin_*
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --profile ci --package nu_plugin_*
|
|
||||||
|
41
.github/workflows/manual.yml
vendored
Normal file
41
.github/workflows/manual.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# This is a basic workflow that is manually triggered
|
||||||
|
# Don't run it unless you know what you are doing
|
||||||
|
|
||||||
|
name: Manual Workflow for Winget Submission
|
||||||
|
|
||||||
|
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||||
|
# or API.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# Inputs the workflow accepts.
|
||||||
|
inputs:
|
||||||
|
ver:
|
||||||
|
# Friendly description to be shown in the UI instead of 'ver'
|
||||||
|
description: 'The nushell version to release'
|
||||||
|
# Default value if no value is explicitly provided
|
||||||
|
default: '0.66.0'
|
||||||
|
# Input has to be provided for the workflow to run
|
||||||
|
required: true
|
||||||
|
uri:
|
||||||
|
# Friendly description to be shown in the UI instead of 'uri'
|
||||||
|
description: 'The nushell windows .msi package URI to publish'
|
||||||
|
# Default value if no value is explicitly provided
|
||||||
|
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
|
||||||
|
# Input has to be provided for the workflow to run
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job
|
||||||
|
rls-winget-pkg:
|
||||||
|
name: Publish winget package manually
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Runs commands using the runners shell
|
||||||
|
- name: Submit package to Windows Package Manager Community Repository Manually
|
||||||
|
run: |
|
||||||
|
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||||
|
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}
|
188
.github/workflows/release-pkg.nu
vendored
Executable file
188
.github/workflows/release-pkg.nu
vendored
Executable file
@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env nu
|
||||||
|
|
||||||
|
# Created: 2022/05/26 19:05:20
|
||||||
|
# Description:
|
||||||
|
# A script to do the github release task, need nushell to be installed.
|
||||||
|
# REF:
|
||||||
|
# 1. https://github.com/volks73/cargo-wix
|
||||||
|
|
||||||
|
# Added 2022-11-29 when Windows packaging wouldn't work
|
||||||
|
# because softprops/action-gh-release was broken
|
||||||
|
# To run this manual for windows
|
||||||
|
# let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||||
|
# let-env TARGET_RUSTFLAGS = ''
|
||||||
|
# let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||||
|
# Pass 1 let-env _EXTRA_ = 'bin'
|
||||||
|
# Pass 2 let-env _EXTRA_ = 'msi'
|
||||||
|
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
||||||
|
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
||||||
|
# make sure you have the wixtools installed https://wixtoolset.org/
|
||||||
|
# set os below like this because it's what github's runner is named
|
||||||
|
# let os = 'windows-latest'
|
||||||
|
|
||||||
|
|
||||||
|
# The main binary file to be released
|
||||||
|
let bin = 'nu'
|
||||||
|
let os = $env.OS
|
||||||
|
let target = $env.TARGET
|
||||||
|
# Repo source dir like `/home/runner/work/nushell/nushell`
|
||||||
|
let src = $env.GITHUB_WORKSPACE
|
||||||
|
let flags = $env.TARGET_RUSTFLAGS
|
||||||
|
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
||||||
|
let version = (open Cargo.toml | get package.version)
|
||||||
|
|
||||||
|
$'Debugging info:'
|
||||||
|
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
||||||
|
|
||||||
|
# $env
|
||||||
|
|
||||||
|
let USE_UBUNTU = 'ubuntu-20.04'
|
||||||
|
|
||||||
|
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||||
|
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||||
|
|
||||||
|
$'Start building ($bin)...'; hr-line
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Build for Ubuntu and macOS
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||||
|
if $os == $USE_UBUNTU {
|
||||||
|
sudo apt update
|
||||||
|
sudo apt-get install libxcb-composite0-dev -y
|
||||||
|
}
|
||||||
|
if $target == 'aarch64-unknown-linux-gnu' {
|
||||||
|
sudo apt-get install gcc-aarch64-linux-gnu -y
|
||||||
|
let-env CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
|
||||||
|
cargo-build-nu $flags
|
||||||
|
} else if $target == 'armv7-unknown-linux-gnueabihf' {
|
||||||
|
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
||||||
|
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||||
|
cargo-build-nu $flags
|
||||||
|
} else if $target == 'riscv64gc-unknown-linux-gnu' {
|
||||||
|
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||||
|
let-env CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
|
||||||
|
cargo-build-nu $flags
|
||||||
|
} else {
|
||||||
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
|
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||||
|
cargo-build-nu $flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Build for Windows without static-link-openssl feature
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if $os in ['windows-latest'] {
|
||||||
|
if ($flags | str trim | is-empty) {
|
||||||
|
cargo build --release --all --target $target
|
||||||
|
} else {
|
||||||
|
cargo build --release --all --target $target $flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Prepare for the release archive
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
let suffix = if $os == 'windows-latest' { '.exe' }
|
||||||
|
# nu, nu_plugin_* were all included
|
||||||
|
let executable = $'target/($target)/release/($bin)*($suffix)'
|
||||||
|
$'Current executable file: ($executable)'
|
||||||
|
|
||||||
|
cd $src; mkdir $dist;
|
||||||
|
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
|
||||||
|
$'(char nl)All executable files:'; hr-line
|
||||||
|
ls -f $executable
|
||||||
|
|
||||||
|
$'(char nl)Copying release files...'; hr-line
|
||||||
|
cp -v README.release.txt $'($dist)/README.txt'
|
||||||
|
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
||||||
|
|
||||||
|
$'(char nl)Check binary release version detail:'; hr-line
|
||||||
|
let ver = if $os == 'windows-latest' {
|
||||||
|
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||||
|
} else {
|
||||||
|
(do -i { ./output/nu -c 'version' }) | str join
|
||||||
|
}
|
||||||
|
if ($ver | str trim | is-empty) {
|
||||||
|
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||||
|
} else { $ver }
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Create a release archive and send it to output for the following steps
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||||
|
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||||
|
|
||||||
|
let files = (ls | get name)
|
||||||
|
let dest = $'($bin)-($version)-($target)'
|
||||||
|
let archive = $'($dist)/($dest).tar.gz'
|
||||||
|
|
||||||
|
mkdir $dest
|
||||||
|
$files | each {|it| mv $it $dest } | ignore
|
||||||
|
|
||||||
|
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||||
|
|
||||||
|
tar -czf $archive $dest
|
||||||
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
|
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
||||||
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
|
} else if $os == 'windows-latest' {
|
||||||
|
|
||||||
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
|
||||||
|
$'(char nl)Download less related stuffs...'; hr-line
|
||||||
|
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||||
|
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||||
|
|
||||||
|
# Create Windows msi release package
|
||||||
|
if (get-env _EXTRA_) == 'msi' {
|
||||||
|
|
||||||
|
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
||||||
|
$'(char nl)Start creating Windows msi package...'
|
||||||
|
cd $src; hr-line
|
||||||
|
# Wix need the binaries be stored in target/release/
|
||||||
|
cp -r $'($dist)/*' target/release/
|
||||||
|
cargo install cargo-wix --version 0.3.3
|
||||||
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
|
print $'archive: ---> ($wixRelease)';
|
||||||
|
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||||
|
let archive = $'($dist)/($releaseStem).zip'
|
||||||
|
7z a $archive *
|
||||||
|
print $'archive: ---> ($archive)';
|
||||||
|
let pkg = (ls -f $archive | get name)
|
||||||
|
if not ($pkg | is-empty) {
|
||||||
|
echo $"archive=($pkg | get 0)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def 'cargo-build-nu' [ options: string ] {
|
||||||
|
if ($options | str trim | is-empty) {
|
||||||
|
cargo build --release --all --target $target --features=static-link-openssl
|
||||||
|
} else {
|
||||||
|
cargo build --release --all --target $target --features=static-link-openssl $options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a horizontal line marker
|
||||||
|
def 'hr-line' [
|
||||||
|
--blank-line(-b): bool
|
||||||
|
] {
|
||||||
|
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||||
|
if $blank_line { char nl }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the specified env key's value or ''
|
||||||
|
def 'get-env' [
|
||||||
|
key: string # The key to get it's env value
|
||||||
|
default: string = '' # The default value for an empty env
|
||||||
|
] {
|
||||||
|
$env | get -i $key | default $default
|
||||||
|
}
|
520
.github/workflows/release.yml
vendored
520
.github/workflows/release.yml
vendored
@ -1,3 +1,7 @@
|
|||||||
|
#
|
||||||
|
# REF:
|
||||||
|
# 1. https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
|
||||||
|
#
|
||||||
name: Create Release Draft
|
name: Create Release Draft
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@ -5,434 +9,92 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
all:
|
||||||
name: Build Linux
|
name: All
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- aarch64-apple-darwin
|
||||||
|
- x86_64-apple-darwin
|
||||||
|
- x86_64-pc-windows-msvc
|
||||||
|
- x86_64-unknown-linux-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
- aarch64-unknown-linux-gnu
|
||||||
|
- armv7-unknown-linux-gnueabihf
|
||||||
|
- riscv64gc-unknown-linux-gnu
|
||||||
|
extra: ['bin']
|
||||||
|
include:
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: 'bin'
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: msi
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: aarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- uses: actions/checkout@v3.1.0
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
- name: Update Rust Toolchain Target
|
||||||
- name: Install libxcb
|
run: |
|
||||||
run: sudo apt-get install libxcb-composite0-dev
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Set up cargo
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
- name: Setup Nushell
|
||||||
toolchain: stable
|
uses: hustcer/setup-nu@v3
|
||||||
override: true
|
with:
|
||||||
|
version: 0.72.1
|
||||||
- name: Build
|
env:
|
||||||
uses: actions-rs/cargo@v1
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
|
||||||
command: build
|
- name: Release Nu Binary
|
||||||
args: --release --all --features=extra,static-link-openssl
|
id: nu
|
||||||
|
run: nu .github/workflows/release-pkg.nu
|
||||||
# - name: Strip binaries (nu)
|
env:
|
||||||
# run: strip target/release/nu
|
OS: ${{ matrix.os }}
|
||||||
|
REF: ${{ github.ref }}
|
||||||
# - name: Strip binaries (nu_plugin_inc)
|
TARGET: ${{ matrix.target }}
|
||||||
# run: strip target/release/nu_plugin_inc
|
_EXTRA_: ${{ matrix.extra }}
|
||||||
|
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
|
||||||
# - name: Strip binaries (nu_plugin_match)
|
|
||||||
# run: strip target/release/nu_plugin_match
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
|
- name: Publish Archive
|
||||||
# - name: Strip binaries (nu_plugin_textview)
|
uses: softprops/action-gh-release@v0.1.13
|
||||||
# run: strip target/release/nu_plugin_textview
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
with:
|
||||||
# - name: Strip binaries (nu_plugin_binaryview)
|
draft: true
|
||||||
# run: strip target/release/nu_plugin_binaryview
|
files: ${{ steps.nu.outputs.archive }}
|
||||||
|
env:
|
||||||
# - name: Strip binaries (nu_plugin_chart_bar)
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# run: strip target/release/nu_plugin_chart_bar
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_chart_line)
|
|
||||||
# run: strip target/release/nu_plugin_chart_line
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_bson)
|
|
||||||
# run: strip target/release/nu_plugin_from_bson
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_sqlite)
|
|
||||||
# run: strip target/release/nu_plugin_from_sqlite
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_mp4)
|
|
||||||
# run: strip target/release/nu_plugin_from_mp4
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_query_json)
|
|
||||||
# run: strip target/release/nu_plugin_query_json
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_s3)
|
|
||||||
# run: strip target/release/nu_plugin_s3
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_selector)
|
|
||||||
# run: strip target/release/nu_plugin_selector
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_start)
|
|
||||||
# run: strip target/release/nu_plugin_start
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_bson)
|
|
||||||
# run: strip target/release/nu_plugin_to_bson
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_sqlite)
|
|
||||||
# run: strip target/release/nu_plugin_to_sqlite
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_tree)
|
|
||||||
# run: strip target/release/nu_plugin_tree
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_xpath)
|
|
||||||
# run: strip target/release/nu_plugin_xpath
|
|
||||||
|
|
||||||
- name: Create output directory
|
|
||||||
run: mkdir output
|
|
||||||
|
|
||||||
- name: Copy files to output
|
|
||||||
run: |
|
|
||||||
cp target/release/nu target/release/nu_plugin_* output/
|
|
||||||
cp README.release.txt output/README.txt
|
|
||||||
cp LICENSE output/LICENSE
|
|
||||||
rm output/*.d
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux
|
|
||||||
path: output/*
|
|
||||||
|
|
||||||
macos:
|
|
||||||
name: Build macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up cargo
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --all --features=extra,static-link-openssl
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu)
|
|
||||||
# run: strip target/release/nu
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_inc)
|
|
||||||
# run: strip target/release/nu_plugin_inc
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_match)
|
|
||||||
# run: strip target/release/nu_plugin_match
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_textview)
|
|
||||||
# run: strip target/release/nu_plugin_textview
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_binaryview)
|
|
||||||
# run: strip target/release/nu_plugin_binaryview
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_chart_bar)
|
|
||||||
# run: strip target/release/nu_plugin_chart_bar
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_chart_line)
|
|
||||||
# run: strip target/release/nu_plugin_chart_line
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_bson)
|
|
||||||
# run: strip target/release/nu_plugin_from_bson
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_sqlite)
|
|
||||||
# run: strip target/release/nu_plugin_from_sqlite
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_mp4)
|
|
||||||
# run: strip target/release/nu_plugin_from_mp4
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_query_json)
|
|
||||||
# run: strip target/release/nu_plugin_query_json
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_s3)
|
|
||||||
# run: strip target/release/nu_plugin_s3
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_selector)
|
|
||||||
# run: strip target/release/nu_plugin_selector
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_start)
|
|
||||||
# run: strip target/release/nu_plugin_start
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_bson)
|
|
||||||
# run: strip target/release/nu_plugin_to_bson
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_sqlite)
|
|
||||||
# run: strip target/release/nu_plugin_to_sqlite
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_tree)
|
|
||||||
# run: strip target/release/nu_plugin_tree
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_xpath)
|
|
||||||
# run: strip target/release/nu_plugin_xpath
|
|
||||||
|
|
||||||
- name: Create output directory
|
|
||||||
run: mkdir output
|
|
||||||
|
|
||||||
- name: Copy files to output
|
|
||||||
run: |
|
|
||||||
cp target/release/nu target/release/nu_plugin_* output/
|
|
||||||
cp README.release.txt output/README.txt
|
|
||||||
cp LICENSE output/LICENSE
|
|
||||||
rm output/*.d
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: macos
|
|
||||||
path: output/*
|
|
||||||
|
|
||||||
windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up cargo
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Add cargo-wix subcommand
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: install
|
|
||||||
args: cargo-wix --version 0.3.1
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --all --features=extra,static-link-openssl
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu.exe)
|
|
||||||
# run: strip target/release/nu.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_inc.exe)
|
|
||||||
# run: strip target/release/nu_plugin_inc.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_match.exe)
|
|
||||||
# run: strip target/release/nu_plugin_match.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_textview.exe)
|
|
||||||
# run: strip target/release/nu_plugin_textview.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_binaryview.exe)
|
|
||||||
# run: strip target/release/nu_plugin_binaryview.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_chart_bar.exe)
|
|
||||||
# run: strip target/release/nu_plugin_chart_bar.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_chart_line.exe)
|
|
||||||
# run: strip target/release/nu_plugin_chart_line.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_bson.exe)
|
|
||||||
# run: strip target/release/nu_plugin_from_bson.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_sqlite.exe)
|
|
||||||
# run: strip target/release/nu_plugin_from_sqlite.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_from_mp4.exe)
|
|
||||||
# run: strip target/release/nu_plugin_from_mp4.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_query_json.exe)
|
|
||||||
# run: strip target/release/nu_plugin_query_json.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_s3.exe)
|
|
||||||
# run: strip target/release/nu_plugin_s3.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_selector.exe)
|
|
||||||
# run: strip target/release/nu_plugin_selector.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_start.exe)
|
|
||||||
# run: strip target/release/nu_plugin_start.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_bson.exe)
|
|
||||||
# run: strip target/release/nu_plugin_to_bson.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_to_sqlite.exe)
|
|
||||||
# run: strip target/release/nu_plugin_to_sqlite.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_tree.exe)
|
|
||||||
# run: strip target/release/nu_plugin_tree.exe
|
|
||||||
|
|
||||||
# - name: Strip binaries (nu_plugin_xpath.exe)
|
|
||||||
# run: strip target/release/nu_plugin_xpath.exe
|
|
||||||
|
|
||||||
- name: Create output directory
|
|
||||||
run: mkdir output
|
|
||||||
|
|
||||||
- name: Download Less Binary
|
|
||||||
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
|
|
||||||
|
|
||||||
- name: Download Less License
|
|
||||||
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
|
|
||||||
|
|
||||||
- name: Copy files to output
|
|
||||||
run: |
|
|
||||||
cp target\release\nu.exe output\
|
|
||||||
cp LICENSE output\
|
|
||||||
cp target\release\LICENSE-for-less.txt output\
|
|
||||||
cp target\release\nu_plugin_*.exe output\
|
|
||||||
cp README.release.txt output\README.txt
|
|
||||||
cp target\release\less.exe output\
|
|
||||||
# Note: If the version of `less.exe` needs to be changed, update this URL
|
|
||||||
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
|
|
||||||
# moved this stuff down to create wix after we download less
|
|
||||||
|
|
||||||
- name: Create msi with wix
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: wix
|
|
||||||
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
|
||||||
|
|
||||||
- name: Upload installer
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: windows-installer
|
|
||||||
path: target\wix\nushell-windows.msi
|
|
||||||
|
|
||||||
- name: Upload zip
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: windows-zip
|
|
||||||
path: output\*
|
|
||||||
|
|
||||||
release:
|
|
||||||
name: Publish Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- linux
|
|
||||||
- macos
|
|
||||||
- windows
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Determine Release Info
|
|
||||||
id: info
|
|
||||||
env:
|
|
||||||
GITHUB_REF: ${{ github.ref }}
|
|
||||||
run: |
|
|
||||||
VERSION=${GITHUB_REF##*/}
|
|
||||||
MAJOR=${VERSION%%.*}
|
|
||||||
MINOR=${VERSION%.*}
|
|
||||||
MINOR=${MINOR#*.}
|
|
||||||
PATCH=${VERSION##*.}
|
|
||||||
echo "::set-output name=version::${VERSION}"
|
|
||||||
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
|
|
||||||
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
|
||||||
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
|
||||||
echo "::set-output name=innerdir::nushell-${VERSION}"
|
|
||||||
|
|
||||||
- name: Create Release Draft
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ steps.info.outputs.version }} Release
|
|
||||||
draft: true
|
|
||||||
|
|
||||||
- name: Create Linux Directory
|
|
||||||
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Download Linux Artifacts
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: linux
|
|
||||||
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Restore Linux File Modes
|
|
||||||
run: |
|
|
||||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
|
||||||
|
|
||||||
- name: Create Linux tarball
|
|
||||||
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
|
||||||
|
|
||||||
- name: Upload Linux Artifact
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
|
||||||
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
|
|
||||||
- name: Create macOS Directory
|
|
||||||
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Download macOS Artifacts
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: macos
|
|
||||||
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Restore macOS File Modes
|
|
||||||
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
|
||||||
|
|
||||||
- name: Create macOS Archive
|
|
||||||
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
|
||||||
|
|
||||||
- name: Upload macOS Artifact
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
|
||||||
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
|
||||||
asset_content_type: application/zip
|
|
||||||
|
|
||||||
- name: Create Windows Directory
|
|
||||||
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Download Windows zip
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: windows-zip
|
|
||||||
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Show Windows Artifacts
|
|
||||||
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
|
||||||
|
|
||||||
- name: Create macOS Archive
|
|
||||||
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
|
||||||
|
|
||||||
- name: Upload Windows zip
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
|
||||||
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
|
||||||
asset_content_type: application/zip
|
|
||||||
|
|
||||||
- name: Download Windows installer
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: windows-installer
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Upload Windows installer
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ./nushell-windows.msi
|
|
||||||
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
|
|
||||||
asset_content_type: application/x-msi
|
|
||||||
|
6
.github/workflows/winget-submission.yml
vendored
6
.github/workflows/winget-submission.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
run: |
|
run: |
|
||||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
|
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||||
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -22,6 +22,20 @@ debian/nu/
|
|||||||
# VSCode's IDE items
|
# VSCode's IDE items
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
# Visual Studio Extension SourceGear Rust items
|
||||||
|
VSWorkspaceSettings.json
|
||||||
|
unstable_cargo_features.txt
|
||||||
|
|
||||||
# Helix configuration folder
|
# Helix configuration folder
|
||||||
.helix/*
|
.helix/*
|
||||||
.helix
|
.helix
|
||||||
|
|
||||||
|
# Coverage tools
|
||||||
|
lcov.info
|
||||||
|
tarpaulin-report.html
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/*
|
||||||
|
*.rsproj
|
||||||
|
*.rsproj.user
|
||||||
|
*.sln
|
@ -1,21 +1,29 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Welcome to nushell!
|
Welcome to Nushell and thank you for considering contributing!
|
||||||
|
|
||||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
## Review Process
|
||||||
|
|
||||||
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.
|
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||||
|
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
||||||
|
So, please, reach out and tell us what you want to do.
|
||||||
|
This will significantly increase the chance of your PR being accepted.
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
The review process can be summarized as follows:
|
||||||
|
1. You want to make some change to Nushell that is more involved than simple bug-fixing.
|
||||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
2. Go to [Discord](https://discordapp.com/invite/NtAbbGn) or a [GitHub issue](https://github.com/nushell/nushell/issues/new/choose) and chat with some core team members and/or other contributors about it.
|
||||||
<!--WIP-->
|
3. After getting a green light from the core team, implement the feature, open a pull request (PR) and write a concise but comprehensive description of the change.
|
||||||
|
4. If your PR includes any use-facing features (such as adding a flag to a command), clearly list them in the PR description.
|
||||||
|
5. Then, core team members and other regular contributors will review the PR and suggest changes.
|
||||||
|
6. When we all agree, the PR will be merged.
|
||||||
|
7. If your PR includes any user-facing features, make sure the changes are also reflected in [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||||
|
8. Congratulate yourself, you just improved Nushell! :-)
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
### Set up
|
### Setup
|
||||||
|
|
||||||
This is no different than other Rust projects.
|
Nushell requires a recent Rust toolchain and some dependencies; [refer to the Nu Book for up-to-date requirements](https://www.nushell.sh/book/installation.html#build-from-source). After installing dependencies, you should be able to clone+build Nu like any other Rust project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/nushell/nushell
|
git clone https://github.com/nushell/nushell
|
||||||
@ -23,29 +31,41 @@ cd nushell
|
|||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
It is a good practice to cover your changes with a test. Also, try to think about corner cases and various ways how your changes could break. Cover those in the tests as well.
|
||||||
|
|
||||||
|
Tests can be found in different places:
|
||||||
|
* `/tests`
|
||||||
|
* `src/tests`
|
||||||
|
* command examples
|
||||||
|
* crate-specific tests
|
||||||
|
|
||||||
|
The most comprehensive test suite we have is the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||||
|
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
- Build and run Nushell:
|
- Build and run Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release && cargo run --release
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
- Build and run with extra features:
|
- Build and run with dataframe support.
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --features=extra && cargo run --release --features=extra
|
cargo run --features=dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run Clippy on Nushell:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo clippy --all --features=stable
|
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests:
|
- Run all tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo test --all --features=stable
|
cargo test --workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests for a specific command
|
- Run all tests for a specific command
|
||||||
@ -71,5 +91,11 @@ cargo build
|
|||||||
- To view verbose logs when developing, enable the `trace` log level.
|
- To view verbose logs when developing, enable the `trace` log level.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --features=extra && cargo run --release --features=extra -- --log-level trace
|
cargo run --release -- --log-level trace
|
||||||
|
```
|
||||||
|
|
||||||
|
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
||||||
|
```shell
|
||||||
|
cargo run --release -- --log-level trace --log-target file
|
||||||
|
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||||
```
|
```
|
||||||
|
2898
Cargo.lock
generated
2898
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
121
Cargo.toml
121
Cargo.toml
@ -3,18 +3,24 @@ authors = ["The Nushell Project Developers"]
|
|||||||
default-run = "nu"
|
default-run = "nu"
|
||||||
description = "A new type of shell"
|
description = "A new type of shell"
|
||||||
documentation = "https://www.nushell.sh/book/"
|
documentation = "https://www.nushell.sh/book/"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
exclude = ["images"]
|
exclude = ["images"]
|
||||||
homepage = "https://www.nushell.sh"
|
homepage = "https://www.nushell.sh"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.63.0"
|
version = "0.74.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ version }-{ target }.{ archive-format }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/nu-cli",
|
"crates/nu-cli",
|
||||||
@ -28,55 +34,70 @@ members = [
|
|||||||
"crates/nu_plugin_gstat",
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu_plugin_query",
|
||||||
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.19"
|
chrono = { version = "0.4.23", features = ["serde"] }
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
ctrlc = "3.2.1"
|
ctrlc = "3.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "4.5.0"
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.63.0" }
|
nu-cli = { path="./crates/nu-cli", version = "0.74.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.63.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.74.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.63.0" }
|
nu-command = { path="./crates/nu-command", version = "0.74.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.63.0" }
|
nu-engine = { path="./crates/nu-engine", version = "0.74.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.63.0" }
|
nu-json = { path="./crates/nu-json", version = "0.74.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.63.0" }
|
nu-parser = { path="./crates/nu-parser", version = "0.74.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.63.0" }
|
nu-path = { path="./crates/nu-path", version = "0.74.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.63.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.74.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.63.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.74.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.63.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.74.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.63.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.74.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.63.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.74.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.63.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.74.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.63.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.74.0" }
|
||||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
pretty_env_logger = "0.4.0"
|
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
reedline = { version = "0.6.0", features = ["bashisms"]}
|
is_executable = "1.0.1"
|
||||||
is_executable = "1.0.1"
|
simplelog = "0.12.0"
|
||||||
|
time = "0.3.12"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||||
|
# Our dependencies don't use OpenSSL on Windows
|
||||||
|
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||||
|
signal-hook = { version = "0.3.14", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
nu-test-support = { path="./crates/nu-test-support", version = "0.63.0" }
|
|
||||||
tempfile = "3.2.0"
|
|
||||||
assert_cmd = "2.0.2"
|
|
||||||
pretty_assertions = "1.0.0"
|
|
||||||
serial_test = "0.5.1"
|
|
||||||
hamcrest2 = "0.3.0"
|
|
||||||
rstest = "0.12.0"
|
|
||||||
itertools = "0.10.3"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
embed-resource = "1"
|
winres = "0.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
|
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
|
||||||
|
atty = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path="./crates/nu-test-support", version = "0.74.0" }
|
||||||
|
tempfile = "3.2.0"
|
||||||
|
assert_cmd = "2.0.2"
|
||||||
|
criterion = "0.4"
|
||||||
|
pretty_assertions = "1.0.0"
|
||||||
|
serial_test = "0.8.0"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||||
default = ["plugin", "which-support", "trash-support"]
|
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||||
|
extra = ["default"]
|
||||||
|
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
extra = ["default", "dataframe", "database"]
|
|
||||||
wasi = []
|
wasi = []
|
||||||
|
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
|
|
||||||
@ -89,8 +110,8 @@ trash-support = ["nu-command/trash-support"]
|
|||||||
# Dataframe feature for nushell
|
# Dataframe feature for nushell
|
||||||
dataframe = ["nu-command/dataframe"]
|
dataframe = ["nu-command/dataframe"]
|
||||||
|
|
||||||
# Database commands for nushell
|
# SQLite commands for nushell
|
||||||
database = ["nu-command/database"]
|
sqlite = ["nu-command/sqlite"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s" # Optimize for size
|
opt-level = "s" # Optimize for size
|
||||||
@ -115,3 +136,23 @@ debug = false
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# To use a development version of a dependency please use a global override here
|
||||||
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
|
[patch.crates-io]
|
||||||
|
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||||
|
|
||||||
|
# Criterion benchmarking setup
|
||||||
|
# Run all benchmarks with `cargo bench`
|
||||||
|
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||||
|
[[bench]]
|
||||||
|
name = "encoder_benchmark"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "eval_benchmark"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "parser_benchmark"
|
||||||
|
harness = false
|
9
Cross.toml
Normal file
9
Cross.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||||
|
# Run cross-rs like this:
|
||||||
|
# cross build --target aarch64-unknown-linux-musl --release
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
256
README.md
256
README.md
@ -1,135 +1,106 @@
|
|||||||
# README
|
# Nushell <!-- omit in toc -->
|
||||||
|
|
||||||
[](https://crates.io/crates/nu)
|
[](https://crates.io/crates/nu)
|
||||||

|

|
||||||
[](https://discord.gg/NtAbbGn)
|
[](https://discord.gg/NtAbbGn)
|
||||||
[](https://changelog.com/podcast/363)
|
[](https://changelog.com/podcast/363)
|
||||||
[](https://twitter.com/nu_shell)
|
[](https://twitter.com/nu_shell)
|
||||||

|

|
||||||

|

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

|

|
||||||
|
|
||||||
|
## Table of Contents <!-- omit in toc -->
|
||||||
|
|
||||||
|
- [Status](#status)
|
||||||
|
- [Learning About Nu](#learning-about-nu)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Philosophy](#philosophy)
|
||||||
|
- [Pipelines](#pipelines)
|
||||||
|
- [Opening files](#opening-files)
|
||||||
|
- [Plugins](#plugins)
|
||||||
|
- [Goals](#goals)
|
||||||
|
- [Progress](#progress)
|
||||||
|
- [Officially Supported By](#officially-supported-by)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This project has reached a minimum-viable product level of quality.
|
This project has reached a minimum-viable-product level of quality. Many people use it as their daily driver, but it may be unstable for some commands. Nu's design is subject to change as it matures.
|
||||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
|
||||||
Future releases will work to fill out missing features and improve stability.
|
|
||||||
Its design is also subject to change as it matures.
|
|
||||||
|
|
||||||
Nu comes with a set of built-in commands (listed below).
|
## Learning About Nu
|
||||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
|
||||||
|
|
||||||
## Learning more
|
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
There are a few good resources to learn about Nu.
|
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||||
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
|
||||||
The book focuses on using Nu and its core concepts.
|
|
||||||
|
|
||||||
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://www.nushell.sh/contributor-book/) to help you get started.
|
|
||||||
There are also [good first issues](https://github.com/nushell/nushell/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to help you dive in.
|
|
||||||
|
|
||||||
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 information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Local
|
To quickly install Nu:
|
||||||
|
|
||||||
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.
|
```bash
|
||||||
|
# Linux and macOS
|
||||||
To build Nu, you will need to use the **latest stable (1.60 or later)** version of the compiler.
|
brew install nushell
|
||||||
|
# Windows
|
||||||
Required dependencies:
|
|
||||||
|
|
||||||
- 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 install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
|
||||||
|
|
||||||
For Windows users, you may also need to install the [Microsoft Visual C++ 2015 Redistributables](https://docs.microsoft.com/cpp/windows/latest-supported-vc-redist).
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo install nu
|
|
||||||
```
|
|
||||||
|
|
||||||
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
winget install nushell
|
winget install nushell
|
||||||
```
|
```
|
||||||
|
|
||||||
To install Nu via the [Chocolatey](https://chocolatey.org) package manager:
|
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||||
|
|
||||||
```shell
|
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||||
choco install nushell
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
### Packaging status
|
|
||||||
|
|
||||||
[](https://repology.org/project/nushell/versions)
|
[](https://repology.org/project/nushell/versions)
|
||||||
|
|
||||||
#### Fedora
|
|
||||||
|
|
||||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
Rather than thinking of files and data as raw streams of text, Nu looks at each input as something with structure.
|
||||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
For example, when you list the contents of a directory what you get back is a table of rows, where each row represents an item in that directory.
|
||||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||||
|
|
||||||
### Pipelines
|
### Pipelines
|
||||||
|
|
||||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||||
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
As in 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).
|
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 work in the pipeline fit into one of three categories:
|
||||||
|
|
||||||
- Commands that produce a stream (e.g., `ls`)
|
- Commands that produce a stream (e.g., `ls`)
|
||||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||||
- Commands that consume the output of the pipeline (e.g., `autoview`)
|
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||||
|
|
||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "Dir" | autoview
|
> ls | where type == "dir" | table
|
||||||
───┬────────┬──────┬───────┬──────────────
|
╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||||
# │ name │ type │ size │ modified
|
│ # │ name │ type │ size │ modified │
|
||||||
───┼────────┼──────┼───────┼──────────────
|
├────┼──────────┼──────┼─────────┼───────────────┤
|
||||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
3 │ docs │ Dir │ 192 B │ 50 mins ago
|
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||||
4 │ images │ Dir │ 160 B │ 5 months ago
|
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||||
5 │ src │ Dir │ 128 B │ 1 day ago
|
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||||
6 │ target │ Dir │ 160 B │ 5 days ago
|
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||||
7 │ tests │ Dir │ 192 B │ 3 months ago
|
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||||
───┴────────┴──────┴───────┴──────────────
|
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
|
│ 9 │ target │ dir │ 0 B │ a day ago │
|
||||||
|
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
|
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||||
|
╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
||||||
We could have also written the above:
|
We could have also written the above:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == Dir
|
> ls | where type == "dir"
|
||||||
```
|
```
|
||||||
|
|
||||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||||
@ -137,15 +108,13 @@ For example, we could use the built-in `ps` command to get a list of the running
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ps | where cpu > 0
|
> ps | where cpu > 0
|
||||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
│ # │ pid │ name │ cpu │ mem │ virtual │
|
||||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||||
0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
|
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||||
1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
|
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||||
2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
|
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||||
3 │ 2202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
|
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||||
4 │ 328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
|
|
||||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opening files
|
### Opening files
|
||||||
@ -155,99 +124,75 @@ For example, you can load a .toml file as structured data and explore it:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml
|
> open Cargo.toml
|
||||||
────────────────────┬───────────────────────────
|
╭──────────────────┬────────────────────╮
|
||||||
bin │ [table 18 rows]
|
│ bin │ [table 1 row] │
|
||||||
build-dependencies │ [row serde toml]
|
│ dependencies │ {record 25 fields} │
|
||||||
dependencies │ [row 29 columns]
|
│ dev-dependencies │ {record 8 fields} │
|
||||||
dev-dependencies │ [row nu-test-support]
|
│ features │ {record 10 fields} │
|
||||||
features │ [row 19 columns]
|
│ package │ {record 13 fields} │
|
||||||
package │ [row 12 columns]
|
│ patch │ {record 1 field} │
|
||||||
workspace │ [row members]
|
│ profile │ {record 3 fields} │
|
||||||
────────────────────┴───────────────────────────
|
│ target │ {record 3 fields} │
|
||||||
|
│ workspace │ {record 1 field} │
|
||||||
|
╰──────────────────┴────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
We can pipeline this into a command that gets the contents of one of the columns:
|
We can pipe this into a command that gets the contents of one of the columns:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package
|
> open Cargo.toml | get package
|
||||||
───────────────┬────────────────────────────────────
|
╭───────────────┬────────────────────────────────────╮
|
||||||
authors │ [table 1 rows]
|
│ authors │ [list 1 item] │
|
||||||
default-run │ nu
|
│ default-run │ nu │
|
||||||
description │ A new type of shell
|
│ description │ A new type of shell │
|
||||||
documentation │ https://www.nushell.sh/book/
|
│ documentation │ https://www.nushell.sh/book/ │
|
||||||
edition │ 2018
|
│ edition │ 2018 │
|
||||||
exclude │ [table 1 rows]
|
│ exclude │ [list 1 item] │
|
||||||
homepage │ https://www.nushell.sh
|
│ homepage │ https://www.nushell.sh │
|
||||||
license │ MIT
|
│ license │ MIT │
|
||||||
name │ nu
|
│ metadata │ {record 1 field} │
|
||||||
readme │ README.md
|
│ name │ nu │
|
||||||
repository │ https://github.com/nushell/nushell
|
│ repository │ https://github.com/nushell/nushell │
|
||||||
version │ 0.32.0
|
│ rust-version │ 1.60 │
|
||||||
───────────────┴────────────────────────────────────
|
│ version │ 0.72.0 │
|
||||||
|
╰───────────────┴────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we can use commands outside of Nu once we have the data we want:
|
And if needed we can drill down further:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version
|
> open Cargo.toml | get package.version
|
||||||
0.32.0
|
0.72.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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/configuration.html).
|
|
||||||
|
|
||||||
To set one of these variables, you can use `config set`. For example:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
> 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 simultaneously.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
|
||||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. There are a few examples in the `crates/nu_plugins_*` directories.
|
||||||
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.
|
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, making 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 filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||||
|
|
||||||
|
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||||
|
|
||||||
- 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.
|
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has 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 compatibility with existing platform-specific executables.
|
||||||
|
|
||||||
- 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 expected of modern software in 2022 (and beyond).
|
||||||
|
|
||||||
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
- Nu views data as either structured or 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/book/command_reference.html).
|
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
Nu is under heavy development and will naturally change as it matures. The chart below isn't meant to be exhaustive, but it helps give an idea for some of the areas of development and their relative maturity:
|
||||||
|
|
||||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||||
@ -264,26 +209,21 @@ Nu is in heavy development and will naturally change as it matures and people us
|
|||||||
| Functions | | | | X | | Functions and aliases are supported |
|
| Functions | | | | X | | Functions and aliases are supported |
|
||||||
| Variables | | | | X | | Nu supports variables and environment variables |
|
| Variables | | | | X | | Nu supports variables and environment variables |
|
||||||
| Completions | | | | X | | Completions for filepaths |
|
| Completions | | | | X | | Completions for filepaths |
|
||||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
| Type-checking | | | | x | | Commands check basic types, and input/output types |
|
||||||
|
|
||||||
## Officially Supported By
|
## Officially Supported By
|
||||||
|
|
||||||
Please submit an issue or PR to be added to this list.
|
Please submit an issue or PR to be added to this list.
|
||||||
|
|
||||||
### Integrations
|
|
||||||
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
||||||
- [starship](https://github.com/starship/starship)
|
- [starship](https://github.com/starship/starship)
|
||||||
- [oh-my-posh](https://ohmyposh.dev)
|
- [oh-my-posh](https://ohmyposh.dev)
|
||||||
- [Couchbase Shell](https://couchbase.sh)
|
- [Couchbase Shell](https://couchbase.sh)
|
||||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||||
### Mentions
|
|
||||||
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](CONTRIBUTING.md) for details.
|
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||||
|
|
||||||
Thanks to all the people who already contributed!
|
|
||||||
|
|
||||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||||
|
|
||||||
> register -e json ./nu_plugin_query
|
> register ./nu_plugin_query
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 7.3 KiB |
@ -1,49 +0,0 @@
|
|||||||
#include <winver.h>
|
|
||||||
|
|
||||||
#define VER_FILEVERSION 0,62,1,0
|
|
||||||
#define VER_FILEVERSION_STR "0.62.1"
|
|
||||||
|
|
||||||
#define VER_PRODUCTVERSION 0,62,1,0
|
|
||||||
#define VER_PRODUCTVERSION_STR "0.62.1"
|
|
||||||
|
|
||||||
#ifdef RC_INVOKED
|
|
||||||
|
|
||||||
#ifdef DEBUG // TODO: Actually define DEBUG
|
|
||||||
#define VER_DEBUG VS_FF_DEBUG
|
|
||||||
#else
|
|
||||||
#define VER_DEBUG 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
|
||||||
FILEVERSION VER_FILEVERSION
|
|
||||||
PRODUCTVERSION VER_PRODUCTVERSION
|
|
||||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
|
||||||
FILEFLAGS VER_DEBUG
|
|
||||||
FILEOS VOS__WINDOWS32
|
|
||||||
FILETYPE VFT_APP
|
|
||||||
FILESUBTYPE VFT2_UNKNOWN
|
|
||||||
BEGIN
|
|
||||||
BLOCK "StringFileInfo"
|
|
||||||
BEGIN
|
|
||||||
BLOCK "040904b0"
|
|
||||||
BEGIN
|
|
||||||
VALUE "CompanyName", "nushell"
|
|
||||||
VALUE "FileDescription", "Nushell"
|
|
||||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
|
||||||
VALUE "InternalName", "nu.exe"
|
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2022"
|
|
||||||
VALUE "OriginalFilename", "nu.exe"
|
|
||||||
VALUE "ProductName", "Nushell"
|
|
||||||
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
|
|
||||||
END
|
|
||||||
END
|
|
||||||
|
|
||||||
BLOCK "VarFileInfo"
|
|
||||||
BEGIN
|
|
||||||
VALUE "Translation", 0x409, 1200
|
|
||||||
END
|
|
||||||
END
|
|
||||||
|
|
||||||
#define IDI_ICON 0x101
|
|
||||||
IDI_ICON ICON "assets/nu_logo.ico"
|
|
||||||
#endif
|
|
7
benches/README.md
Normal file
7
benches/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Criterion benchmarks
|
||||||
|
|
||||||
|
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
||||||
|
|
||||||
|
Run all benchmarks with `cargo bench`
|
||||||
|
|
||||||
|
Or run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
76
benches/encoder_benchmark.rs
Normal file
76
benches/encoder_benchmark.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use nu_plugin::{EncodingType, PluginResponse};
|
||||||
|
use nu_protocol::{Span, Value};
|
||||||
|
|
||||||
|
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||||
|
fn new_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
||||||
|
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
|
||||||
|
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
|
||||||
|
|
||||||
|
Value::List {
|
||||||
|
vals: (0..row_cnt)
|
||||||
|
.map(|_| Value::test_record(columns.clone(), vals.clone()))
|
||||||
|
.collect(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_encoding(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Encoding");
|
||||||
|
let test_cnt_pairs = [
|
||||||
|
(100, 5),
|
||||||
|
(100, 10),
|
||||||
|
(100, 15),
|
||||||
|
(1000, 5),
|
||||||
|
(1000, 10),
|
||||||
|
(1000, 15),
|
||||||
|
(10000, 5),
|
||||||
|
(10000, 10),
|
||||||
|
(10000, 15),
|
||||||
|
];
|
||||||
|
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||||
|
for fmt in ["json", "msgpack"] {
|
||||||
|
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
||||||
|
let mut res = vec![];
|
||||||
|
let test_data = PluginResponse::Value(Box::new(new_test_data(row_cnt, col_cnt)));
|
||||||
|
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||||
|
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_decoding(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Decoding");
|
||||||
|
let test_cnt_pairs = [
|
||||||
|
(100, 5),
|
||||||
|
(100, 10),
|
||||||
|
(100, 15),
|
||||||
|
(1000, 5),
|
||||||
|
(1000, 10),
|
||||||
|
(1000, 15),
|
||||||
|
(10000, 5),
|
||||||
|
(10000, 10),
|
||||||
|
(10000, 15),
|
||||||
|
];
|
||||||
|
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||||
|
for fmt in ["json", "msgpack"] {
|
||||||
|
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
||||||
|
let mut res = vec![];
|
||||||
|
let test_data = PluginResponse::Value(Box::new(new_test_data(row_cnt, col_cnt)));
|
||||||
|
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||||
|
encoder.encode_response(&test_data, &mut res).unwrap();
|
||||||
|
let mut binary_data = std::io::Cursor::new(res);
|
||||||
|
b.iter(|| {
|
||||||
|
binary_data.set_position(0);
|
||||||
|
encoder.decode_response(&mut binary_data)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, bench_encoding, bench_decoding);
|
||||||
|
criterion_main!(benches);
|
42
benches/eval_benchmark.rs
Normal file
42
benches/eval_benchmark.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use nu_cli::eval_source;
|
||||||
|
use nu_protocol::{PipelineData, Span, Value};
|
||||||
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
c.bench_function("eval default_env.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_env().as_bytes(),
|
||||||
|
"default_env.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("eval default_config.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
// parsing config.nu breaks without PWD set
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||||
|
);
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_config().as_bytes(),
|
||||||
|
"default_config.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
34
benches/parser_benchmark.rs
Normal file
34
benches/parser_benchmark.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{Span, Value};
|
||||||
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
// parsing config.nu breaks without PWD set
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let default_env = get_default_env().as_bytes();
|
||||||
|
c.bench_function("parse_default_env_file", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||||
|
|mut working_set| parse(&mut working_set, None, default_env, false, &[]),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let default_config = get_default_config().as_bytes();
|
||||||
|
c.bench_function("parse_default_config_file", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||||
|
|mut working_set| parse(&mut working_set, None, default_config, false, &[]),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
echo "---------------------------------------------------------------"
|
echo "---------------------------------------------------------------"
|
||||||
echo "Building nushell (nu) with --features=extra and all the plugins"
|
echo "Building nushell (nu) with dataframes and all the plugins"
|
||||||
echo "---------------------------------------------------------------"
|
echo "---------------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@ -10,13 +11,14 @@ NU_PLUGINS=(
|
|||||||
'nu_plugin_gstat'
|
'nu_plugin_gstat'
|
||||||
'nu_plugin_inc'
|
'nu_plugin_inc'
|
||||||
'nu_plugin_query'
|
'nu_plugin_query'
|
||||||
|
'nu_plugin_custom_values'
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Building nushell"
|
echo "Building nushell"
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
for plugin in "${NU_PLUGINS[@]}"
|
for plugin in "${NU_PLUGINS[@]}"
|
||||||
do
|
do
|
||||||
echo '' && cd crates/$plugin
|
echo '' && cd crates/"$plugin"
|
||||||
echo "Building $plugin..."
|
echo "Building $plugin..."
|
||||||
echo "-----------------------------"
|
echo "-----------------------------"
|
||||||
cargo build && cd ../..
|
cargo build && cd ../..
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
@echo off
|
@echo off
|
||||||
@echo -------------------------------------------------------------------
|
@echo -------------------------------------------------------------------
|
||||||
@echo Building nushell (nu.exe) with --features=extra and all the plugins
|
@echo Building nushell (nu.exe) with dataframes and all the plugins
|
||||||
@echo -------------------------------------------------------------------
|
@echo -------------------------------------------------------------------
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
echo Building nushell.exe
|
echo Building nushell.exe
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd crates\nu_plugin_example
|
@cd crates\nu_plugin_example
|
||||||
@ -24,9 +24,13 @@ cargo build
|
|||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd ..\..\crates\nu_plugin_query
|
@cd ..\..\crates\nu_plugin_query
|
||||||
|
|
||||||
echo Building nu_plugin_query.exe
|
echo Building nu_plugin_query.exe
|
||||||
cargo build
|
cargo build
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
|
@cd ..\..\crates\nu_plugin_custom_values
|
||||||
|
echo Building nu_plugin_custom_values.exe
|
||||||
|
cargo build
|
||||||
|
@echo.
|
||||||
|
|
||||||
@cd ..\..
|
@cd ..\..
|
@ -1,16 +1,17 @@
|
|||||||
echo '-------------------------------------------------------------------'
|
echo '-------------------------------------------------------------------'
|
||||||
echo 'Building nushell (nu) with --features=extra and all the plugins'
|
echo 'Building nushell (nu) with dataframes and all the plugins'
|
||||||
echo '-------------------------------------------------------------------'
|
echo '-------------------------------------------------------------------'
|
||||||
|
|
||||||
echo $'(char nl)Building nushell'
|
echo $'(char nl)Building nushell'
|
||||||
echo '----------------------------'
|
echo '----------------------------'
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
|
|
||||||
let plugins = [
|
let plugins = [
|
||||||
nu_plugin_inc,
|
nu_plugin_inc,
|
||||||
nu_plugin_gstat,
|
nu_plugin_gstat,
|
||||||
nu_plugin_query,
|
nu_plugin_query,
|
||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
|
nu_plugin_custom_values,
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
8
build.rs
8
build.rs
@ -1,6 +1,12 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn main() {
|
fn main() {
|
||||||
embed_resource::compile_for("assets/nushell.rc", &["nu"])
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set("ProductName", "Nushell");
|
||||||
|
res.set("FileDescription", "Nushell");
|
||||||
|
res.set("LegalCopyright", "Copyright (C) 2022");
|
||||||
|
res.set_icon("assets/nu_logo.ico");
|
||||||
|
res.compile()
|
||||||
|
.expect("Failed to run the Windows resource compiler (rc.exe)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -1,31 +1,39 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "CLI-related functionality for Nushell"
|
description = "CLI-related functionality for Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.63.0"
|
version = "0.74.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.63.0" }
|
nu-test-support = { path="../nu-test-support", version = "0.74.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.63.0" }
|
nu-command = { path = "../nu-command", version = "0.74.0" }
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.63.0" }
|
nu-engine = { path = "../nu-engine", version = "0.74.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.63.0" }
|
nu-path = { path = "../nu-path", version = "0.74.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.63.0" }
|
nu-parser = { path = "../nu-parser", version = "0.74.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.74.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.63.0" }
|
nu-utils = { path = "../nu-utils", version = "0.74.0" }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.63.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.74.0" }
|
||||||
reedline = { version = "0.6.0", features = ["bashisms"]}
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
crossterm = "0.23.0"
|
|
||||||
miette = { version = "4.5.0", features = ["fancy"] }
|
|
||||||
thiserror = "1.0.29"
|
|
||||||
fuzzy-matcher = "0.3.7"
|
|
||||||
|
|
||||||
log = "0.4"
|
atty = "0.2.14"
|
||||||
|
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
||||||
|
crossterm = "0.24.0"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
|
fuzzy-matcher = "0.3.7"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
|
once_cell = "1.16.0"
|
||||||
|
log = "0.4"
|
||||||
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
|
percent-encoding = "2"
|
||||||
|
sysinfo = "0.26.2"
|
||||||
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -5,21 +5,26 @@ use nu_engine::{convert_env_values, eval_block};
|
|||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::Stack;
|
use nu_protocol::engine::Stack;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
PipelineData, Spanned, Value,
|
PipelineData, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
init_cwd: &Path,
|
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
table_mode: Option<Value>,
|
table_mode: Option<Value>,
|
||||||
) -> Result<()> {
|
) -> Result<Option<i64>> {
|
||||||
// Run a command (or commands) given to us by the user
|
// Translate environment variables from Strings to Values
|
||||||
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source code
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
@ -39,43 +44,19 @@ pub fn evaluate_commands(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
// Update permanent state
|
||||||
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = engine_state.get_config().clone();
|
// Run the block
|
||||||
if let Some(t_mode) = table_mode {
|
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
config.table_mode = t_mode.as_string()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
|
||||||
Ok(cwd) => {
|
|
||||||
if let Err(e) =
|
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
|
let mut config = engine_state.get_config().clone();
|
||||||
|
if let Some(t_mode) = table_mode {
|
||||||
|
config.table_mode = t_mode.as_string()?;
|
||||||
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -84,11 +65,9 @@ pub fn evaluate_commands(
|
|||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(exit_code)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
|||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandCompletion {
|
impl CommandCompletion {
|
||||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
|||||||
_: &StateWorkingSet,
|
_: &StateWorkingSet,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
flattened,
|
flattened,
|
||||||
flat_shape,
|
flat_shape,
|
||||||
|
force_completion_after_space,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,19 +46,21 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if !executables.contains(
|
if self.engine_state.config.max_external_completion_results
|
||||||
&item
|
> executables.len() as i64
|
||||||
.path()
|
&& !executables.contains(
|
||||||
.file_name()
|
&item
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
.path()
|
||||||
.unwrap_or_default(),
|
.file_name()
|
||||||
) && matches!(
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
item.path()
|
.unwrap_or_default(),
|
||||||
.file_name()
|
)
|
||||||
.map(|x| match_algorithm
|
&& matches!(
|
||||||
|
item.path().file_name().map(|x| match_algorithm
|
||||||
.matches_str(&x.to_string_lossy(), prefix)),
|
.matches_str(&x.to_string_lossy(), prefix)),
|
||||||
Some(true)
|
Some(true)
|
||||||
) && is_executable::is_executable(&item.path())
|
)
|
||||||
|
&& is_executable::is_executable(item.path())
|
||||||
{
|
{
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
executables.push(name);
|
executables.push(name);
|
||||||
@ -89,10 +94,7 @@ impl CommandCompletion {
|
|||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
description: x.1,
|
description: x.1,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -103,10 +105,7 @@ impl CommandCompletion {
|
|||||||
value: String::from_utf8_lossy(&x).to_string(),
|
value: String::from_utf8_lossy(&x).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,7 +113,8 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
let partial = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
let partial = String::from_utf8_lossy(partial).to_string();
|
let partial = String::from_utf8_lossy(partial).to_string();
|
||||||
let results = if find_externals {
|
|
||||||
|
if find_externals {
|
||||||
let results_external = self
|
let results_external = self
|
||||||
.external_command_completion(&partial, match_algorithm)
|
.external_command_completion(&partial, match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -122,15 +122,15 @@ impl CommandCompletion {
|
|||||||
value: x,
|
value: x,
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let results_strings: Vec<String> =
|
||||||
|
results.clone().into_iter().map(|x| x.value).collect();
|
||||||
|
|
||||||
for external in results_external {
|
for external in results_external {
|
||||||
if results.contains(&external) {
|
if results_strings.contains(&external.value) {
|
||||||
results.push(Suggestion {
|
results.push(Suggestion {
|
||||||
value: format!("^{}", external.value),
|
value: format!("^{}", external.value),
|
||||||
description: None,
|
description: None,
|
||||||
@ -146,9 +146,7 @@ impl CommandCompletion {
|
|||||||
results
|
results
|
||||||
} else {
|
} else {
|
||||||
results
|
results
|
||||||
};
|
}
|
||||||
|
|
||||||
results
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,10 +181,7 @@ impl Completer for CommandCompletion {
|
|||||||
let subcommands = if let Some(last) = last {
|
let subcommands = if let Some(last) = last {
|
||||||
self.complete_commands(
|
self.complete_commands(
|
||||||
working_set,
|
working_set,
|
||||||
Span {
|
Span::new(last.0.start, pos),
|
||||||
start: last.0.start,
|
|
||||||
end: pos,
|
|
||||||
},
|
|
||||||
offset,
|
offset,
|
||||||
false,
|
false,
|
||||||
options.match_algorithm,
|
options.match_algorithm,
|
||||||
@ -199,12 +194,23 @@ impl Completer for CommandCompletion {
|
|||||||
return subcommands;
|
return subcommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let config = working_set.get_config();
|
||||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||||
|| ((span.end - span.start) == 0)
|
|| ((span.end - span.start) == 0)
|
||||||
{
|
{
|
||||||
// we're in a gap or at a command
|
// we're in a gap or at a command
|
||||||
self.complete_commands(working_set, span, offset, true, options.match_algorithm)
|
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||||
|
{
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
self.complete_commands(
|
||||||
|
working_set,
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
|
config.enable_external_completion,
|
||||||
|
options.match_algorithm,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,12 @@ use crate::completions::{
|
|||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||||
};
|
};
|
||||||
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
ast::PipelineElement,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
BlockId, PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||||
use std::str;
|
use std::str;
|
||||||
@ -37,7 +39,10 @@ impl NuCompleter {
|
|||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let mut options = CompletionOptions::default();
|
let mut options = CompletionOptions {
|
||||||
|
case_sensitive: config.case_sensitive_completions,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if config.completion_algorithm == "fuzzy" {
|
if config.completion_algorithm == "fuzzy" {
|
||||||
options.match_algorithm = MatchAlgorithm::Fuzzy;
|
options.match_algorithm = MatchAlgorithm::Fuzzy;
|
||||||
@ -53,150 +58,124 @@ impl NuCompleter {
|
|||||||
suggestions
|
suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn external_completion(
|
||||||
|
&self,
|
||||||
|
block_id: BlockId,
|
||||||
|
spans: &[String],
|
||||||
|
offset: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Option<Vec<Suggestion>> {
|
||||||
|
let stack = self.stack.clone();
|
||||||
|
let block = self.engine_state.get_block(block_id);
|
||||||
|
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
|
// Line
|
||||||
|
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
||||||
|
if let Some(var_id) = pos_arg.var_id {
|
||||||
|
callee_stack.add_var(
|
||||||
|
var_id,
|
||||||
|
Value::List {
|
||||||
|
vals: spans
|
||||||
|
.iter()
|
||||||
|
.map(|it| Value::string(it, Span::unknown()))
|
||||||
|
.collect(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = eval_block(
|
||||||
|
&self.engine_state,
|
||||||
|
&mut callee_stack,
|
||||||
|
block,
|
||||||
|
PipelineData::empty(),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(pd) => {
|
||||||
|
let value = pd.into_value(span);
|
||||||
|
if let Value::List { vals, span: _ } = value {
|
||||||
|
let result =
|
||||||
|
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
||||||
|
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => println!("failed to eval completer block: {}", err),
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||||
let initial_line = line.to_string();
|
let initial_line = line.to_string();
|
||||||
let mut line = line.to_string();
|
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||||
line.insert(pos, 'a');
|
new_line.insert(alias_total_offset + pos, b'a');
|
||||||
let pos = offset + pos;
|
let pos = offset + pos;
|
||||||
let (output, _err) = parse(
|
let config = self.engine_state.get_config();
|
||||||
&mut working_set,
|
|
||||||
Some("completer"),
|
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||||
line.as_bytes(),
|
|
||||||
false,
|
|
||||||
&[],
|
|
||||||
);
|
|
||||||
|
|
||||||
for pipeline in output.pipelines.into_iter() {
|
for pipeline in output.pipelines.into_iter() {
|
||||||
for expr in pipeline.expressions {
|
for pipeline_element in pipeline.elements {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
match pipeline_element {
|
||||||
|
PipelineElement::Expression(_, expr)
|
||||||
|
| PipelineElement::Redirection(_, _, expr)
|
||||||
|
| PipelineElement::And(_, expr)
|
||||||
|
| PipelineElement::Or(_, expr) => {
|
||||||
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
|
let span_offset: usize = alias_offset.iter().sum();
|
||||||
|
let mut spans: Vec<String> = vec![];
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
if pos >= flat.0.start && pos < flat.0.end {
|
// Read the current spam to string
|
||||||
// Context variables
|
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||||
let most_left_var =
|
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
|
||||||
|
|
||||||
// Create a new span
|
// Skip the last 'a' as span item
|
||||||
let new_span = Span {
|
if flat_idx == flattened.len() - 1 {
|
||||||
start: flat.0.start,
|
let mut chars = current_span_str.chars();
|
||||||
end: flat.0.end - 1,
|
chars.next_back();
|
||||||
};
|
let current_span_str = chars.as_str().to_owned();
|
||||||
|
spans.push(current_span_str.to_string());
|
||||||
|
} else {
|
||||||
|
spans.push(current_span_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
// Parses the prefix
|
// Complete based on the last span
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||||
prefix.remove(pos - flat.0.start);
|
// Context variables
|
||||||
|
let most_left_var =
|
||||||
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
|
|
||||||
// Completions that depends on the previous expression (e.g: use, source)
|
// Create a new span
|
||||||
if flat_idx > 0 {
|
let new_span = if flat_idx == 0 {
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
Span::new(flat.0.start, flat.0.end - 1 - span_offset)
|
||||||
// Read the content for the previous expression
|
} else {
|
||||||
let prev_expr_str =
|
Span::new(
|
||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
flat.0.start - span_offset,
|
||||||
|
flat.0.end - 1 - span_offset,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// Completion for .nu files
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
let mut completer =
|
let index = pos - (flat.0.start - span_offset);
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
prefix.drain(index..);
|
||||||
|
|
||||||
return self.process_completion(
|
// Variables completion
|
||||||
&mut completer,
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
&working_set,
|
let mut completer = VariableCompletion::new(
|
||||||
prefix,
|
self.engine_state.clone(),
|
||||||
new_span,
|
self.stack.clone(),
|
||||||
offset,
|
most_left_var.unwrap_or((vec![], vec![])),
|
||||||
pos,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables completion
|
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
|
||||||
let mut completer = VariableCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
self.stack.clone(),
|
|
||||||
most_left_var.unwrap_or((vec![], vec![])),
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags completion
|
|
||||||
if prefix.starts_with(b"-") {
|
|
||||||
let mut completer = FlagCompletion::new(expr);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match other types
|
|
||||||
match &flat.1 {
|
|
||||||
FlatShape::Custom(decl_id) => {
|
|
||||||
let mut completer = CustomCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
self.stack.clone(),
|
|
||||||
*decl_id,
|
|
||||||
initial_line,
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FlatShape::Directory => {
|
|
||||||
let mut completer =
|
|
||||||
DirectoryCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
flat_shape => {
|
|
||||||
let mut completer = CommandCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
&working_set,
|
|
||||||
flattened.clone(),
|
|
||||||
// flat_idx,
|
|
||||||
flat_shape.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let out: Vec<_> = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix.clone(),
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if out.is_empty() {
|
|
||||||
let mut completer =
|
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -208,15 +187,195 @@ impl NuCompleter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
// Flags completion
|
||||||
|
if prefix.starts_with(b"-") {
|
||||||
|
// Try to complete flag internally
|
||||||
|
let mut completer = FlagCompletion::new(expr.clone());
|
||||||
|
let result = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got no results for internal completion
|
||||||
|
// now we can check if external completer is set and use it
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) = self
|
||||||
|
.external_completion(block_id, &spans, offset, new_span)
|
||||||
|
{
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// specially check if it is currently empty - always complete commands
|
||||||
|
if flat_idx == 0
|
||||||
|
&& working_set.get_span_contents(new_span).is_empty()
|
||||||
|
{
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
FlatShape::String,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||||
|
if flat_idx > 0 {
|
||||||
|
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||||
|
// Read the content for the previous expression
|
||||||
|
let prev_expr_str =
|
||||||
|
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||||
|
|
||||||
|
// Completion for .nu files
|
||||||
|
if prev_expr_str == b"use" || prev_expr_str == b"source-env"
|
||||||
|
{
|
||||||
|
let mut completer =
|
||||||
|
DotNuCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
} else if prev_expr_str == b"ls" {
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match other types
|
||||||
|
match &flat.1 {
|
||||||
|
FlatShape::Custom(decl_id) => {
|
||||||
|
let mut completer = CustomCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
*decl_id,
|
||||||
|
initial_line,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Directory => {
|
||||||
|
let mut completer =
|
||||||
|
DirectoryCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
flat_shape => {
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
flat_shape.clone(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut out: Vec<_> = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to complete using an external completer (if set)
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) = self.external_completion(
|
||||||
|
block_id, &spans, offset, new_span,
|
||||||
|
) {
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for file completion
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
out = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return vec![];
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +385,85 @@ impl ReedlineCompleter for NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||||
|
|
||||||
|
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||||
|
// that `g` is an alias of `git`
|
||||||
|
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
|
||||||
|
// An vector represents the offsets of alias
|
||||||
|
// e.g: the offset is 2 for the alias `g` of `git`
|
||||||
|
let mut alias_offset = vec![];
|
||||||
|
let mut output = vec![];
|
||||||
|
if let Some(matched_alias) = search_alias(line, working_set) {
|
||||||
|
let mut lens = matched_alias.len();
|
||||||
|
for (input_vec, line_vec) in matched_alias {
|
||||||
|
alias_offset.push(line_vec.len() - input_vec.len());
|
||||||
|
output.extend(line_vec);
|
||||||
|
if lens > 1 {
|
||||||
|
output.push(b' ');
|
||||||
|
lens -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !line.is_empty() {
|
||||||
|
let last = line.last().expect("input is empty");
|
||||||
|
if last == &b' ' {
|
||||||
|
output.push(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output = line.to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, alias_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||||
|
let mut vec_names = vec![];
|
||||||
|
let mut vec_alias = vec![];
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut is_alias = false;
|
||||||
|
for (index, character) in input.iter().enumerate() {
|
||||||
|
if *character == b' ' {
|
||||||
|
let range = &input[pos..index];
|
||||||
|
vec_names.push(range.to_owned());
|
||||||
|
pos = index + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Push the rest to names vector.
|
||||||
|
if pos < input.len() {
|
||||||
|
vec_names.push(input[pos..].to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
for name in &vec_names {
|
||||||
|
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||||
|
let alias_span = working_set.get_alias(alias_id);
|
||||||
|
let mut span_vec = vec![];
|
||||||
|
is_alias = true;
|
||||||
|
for alias in alias_span {
|
||||||
|
let name = working_set.get_span_contents(*alias);
|
||||||
|
if !name.is_empty() {
|
||||||
|
span_vec.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
|
||||||
|
let full_aliases = span_vec.join(&[b' '][..]);
|
||||||
|
vec_alias.push(full_aliases);
|
||||||
|
} else {
|
||||||
|
vec_alias.push(name.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_alias {
|
||||||
|
// Zip names and alias vectors, the original inputs and its aliases mapping.
|
||||||
|
// e.g:(['g'], ['g','i','t'])
|
||||||
|
let output = vec_names.into_iter().zip(vec_alias).collect();
|
||||||
|
Some(output)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reads the most left variable returning it's name (e.g: $myvar)
|
// reads the most left variable returning it's name (e.g: $myvar)
|
||||||
// and the depth (a.b.c)
|
// and the depth (a.b.c)
|
||||||
fn most_left_variable(
|
fn most_left_variable(
|
||||||
@ -275,3 +513,65 @@ fn most_left_variable(
|
|||||||
|
|
||||||
Some((var, sublevels))
|
Some((var, sublevels))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_value_completions<'a>(
|
||||||
|
list: impl Iterator<Item = &'a Value>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
|
list.filter_map(move |x| {
|
||||||
|
// Match for string values
|
||||||
|
if let Ok(s) = x.as_string() {
|
||||||
|
return Some(Suggestion {
|
||||||
|
value: s,
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match for record values
|
||||||
|
if let Ok((cols, vals)) = x.as_record() {
|
||||||
|
let mut suggestion = Suggestion {
|
||||||
|
value: String::from(""), // Initialize with empty string
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate the cols looking for `value` and `description`
|
||||||
|
cols.iter().zip(vals).for_each(|it| {
|
||||||
|
// Match `value` column
|
||||||
|
if it.0 == "value" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(val_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.value = val_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match `description` column
|
||||||
|
if it.0 == "description" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(desc_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.description = Some(desc_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Some(suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::completer::map_value_completions;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
|||||||
sort_by: SortBy::None,
|
sort_by: SortBy::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_completions<'a>(
|
|
||||||
&self,
|
|
||||||
list: impl Iterator<Item = &'a Value>,
|
|
||||||
span: Span,
|
|
||||||
offset: usize,
|
|
||||||
) -> Vec<Suggestion> {
|
|
||||||
list.filter_map(move |x| {
|
|
||||||
// Match for string values
|
|
||||||
if let Ok(s) = x.as_string() {
|
|
||||||
return Some(Suggestion {
|
|
||||||
value: s,
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match for record values
|
|
||||||
if let Ok((cols, vals)) = x.as_record() {
|
|
||||||
let mut suggestion = Suggestion {
|
|
||||||
value: String::from(""), // Initialize with empty string
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Iterate the cols looking for `value` and `description`
|
|
||||||
cols.iter().zip(vals).for_each(|it| {
|
|
||||||
// Match `value` column
|
|
||||||
if it.0 == "value" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(val_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.value = val_str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match `description` column
|
|
||||||
if it.0 == "description" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(desc_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.description = Some(desc_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Some(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CustomCompletion {
|
impl Completer for CustomCompletion {
|
||||||
@ -113,13 +52,13 @@ impl Completer for CustomCompletion {
|
|||||||
head: span,
|
head: span,
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument::Positional(Expression {
|
Argument::Positional(Expression {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
ty: Type::String,
|
ty: Type::String,
|
||||||
expr: Expr::String(self.line.clone()),
|
expr: Expr::String(self.line.clone()),
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}),
|
}),
|
||||||
Argument::Positional(Expression {
|
Argument::Positional(Expression {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
ty: Type::Int,
|
ty: Type::Int,
|
||||||
expr: Expr::Int(line_pos as i64),
|
expr: Expr::Int(line_pos as i64),
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
@ -127,8 +66,9 @@ impl Completer for CustomCompletion {
|
|||||||
],
|
],
|
||||||
redirect_stdout: true,
|
redirect_stdout: true,
|
||||||
redirect_stderr: true,
|
redirect_stderr: true,
|
||||||
|
parser_info: vec![],
|
||||||
},
|
},
|
||||||
PipelineData::new(span),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut custom_completion_options = None;
|
let mut custom_completion_options = None;
|
||||||
@ -144,7 +84,7 @@ impl Completer for CustomCompletion {
|
|||||||
.and_then(|val| {
|
.and_then(|val| {
|
||||||
val.as_list()
|
val.as_list()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let options = value.get_data_by_key("options");
|
let options = value.get_data_by_key("options");
|
||||||
@ -189,7 +129,7 @@ impl Completer for CustomCompletion {
|
|||||||
|
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,8 +136,12 @@ pub fn directory_completion(
|
|||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
// Fix files or folders with quotes or hash
|
||||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
if path.contains('\'')
|
||||||
|
|| path.contains('"')
|
||||||
|
|| path.contains(' ')
|
||||||
|
|| path.contains('#')
|
||||||
|
{
|
||||||
path = format!("`{}`", path);
|
path = format!("`{}`", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +141,14 @@ pub fn file_path_completion(
|
|||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
// Fix files or folders with quotes or hashes
|
||||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
if path.contains('\'')
|
||||||
|
|| path.contains('"')
|
||||||
|
|| path.contains(' ')
|
||||||
|
|| path.contains('#')
|
||||||
|
|| path.contains('(')
|
||||||
|
|| path.contains(')')
|
||||||
|
{
|
||||||
path = format!("`{}`", path);
|
path = format!("`{}`", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ use reedline::Suggestion;
|
|||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::MatchAlgorithm;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||||
@ -41,7 +43,7 @@ impl Completer for VariableCompletion {
|
|||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
@ -70,15 +72,28 @@ impl Completer for VariableCompletion {
|
|||||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
self.var_context.1.clone().into_iter().skip(1).collect();
|
||||||
|
|
||||||
if let Some(val) = env_vars.get(&target_var_str) {
|
if let Some(val) = env_vars.get(&target_var_str) {
|
||||||
return nested_suggestions(val.clone(), nested_levels, current_span);
|
for suggestion in
|
||||||
|
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||||
|
{
|
||||||
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
suggestion.value.as_bytes(),
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
|
output.push(suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No nesting provided, return all env vars
|
// No nesting provided, return all env vars
|
||||||
for env_var in env_vars {
|
for env_var in env_vars {
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(env_var.0.as_bytes(), &prefix)
|
env_var.0.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: env_var.0,
|
value: env_var.0,
|
||||||
description: None,
|
description: None,
|
||||||
@ -100,39 +115,55 @@ impl Completer for VariableCompletion {
|
|||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
nu_protocol::NU_VARIABLE_ID,
|
nu_protocol::NU_VARIABLE_ID,
|
||||||
nu_protocol::Span {
|
nu_protocol::Span::new(current_span.start, current_span.end),
|
||||||
start: current_span.start,
|
|
||||||
end: current_span.end,
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
return nested_suggestions(nuval, self.var_context.1.clone(), current_span);
|
for suggestion in
|
||||||
|
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
||||||
|
{
|
||||||
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
suggestion.value.as_bytes(),
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
|
output.push(suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completion other variable types
|
// Completion other variable types
|
||||||
if let Some(var_id) = var_id {
|
if let Some(var_id) = var_id {
|
||||||
// Extract the variable value from the stack
|
// Extract the variable value from the stack
|
||||||
let var = self.stack.get_var(
|
let var = self.stack.get_var(var_id, Span::new(span.start, span.end));
|
||||||
var_id,
|
|
||||||
Span {
|
|
||||||
start: span.start,
|
|
||||||
end: span.end,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the value exists and it's of type Record
|
// If the value exists and it's of type Record
|
||||||
if let Ok(value) = var {
|
if let Ok(value) = var {
|
||||||
return nested_suggestions(value, self.var_context.1.clone(), current_span);
|
for suggestion in
|
||||||
|
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
||||||
|
{
|
||||||
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
suggestion.value.as_bytes(),
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
|
output.push(suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variable completion (e.g: $en<tab> to complete $env)
|
// Variable completion (e.g: $en<tab> to complete $env)
|
||||||
for builtin in builtins {
|
for builtin in builtins {
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(builtin.as_bytes(), &prefix)
|
builtin.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -154,7 +185,11 @@ impl Completer for VariableCompletion {
|
|||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for v in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
v.0,
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -176,7 +211,11 @@ impl Completer for VariableCompletion {
|
|||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for v in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
v.0,
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -248,7 +287,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::Nothing {
|
return Value::Nothing {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => return val,
|
_ => return val,
|
||||||
@ -257,3 +296,13 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MatchAlgorithm {
|
||||||
|
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
|
||||||
|
if sensitive {
|
||||||
|
self.matches_u8(haystack, needle)
|
||||||
|
} else {
|
||||||
|
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,27 +1,36 @@
|
|||||||
use crate::util::{eval_source, report_error};
|
use crate::util::{eval_source, report_error};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use log::info;
|
use log::info;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
#[cfg(feature = "plugin")]
|
||||||
use nu_protocol::{PipelineData, Span};
|
use nu_parser::ParseError;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_path::canonicalize_with;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_protocol::Spanned;
|
||||||
|
use nu_protocol::{HistoryFileFormat, PipelineData};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
const PLUGIN_FILE: &str = "plugin.nu";
|
const PLUGIN_FILE: &str = "plugin.nu";
|
||||||
|
|
||||||
|
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||||
|
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(
|
pub fn read_plugin_file(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
is_perf_true: bool,
|
|
||||||
) {
|
) {
|
||||||
// Reading signatures from signature file
|
// Reading signatures from signature file
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, storage_path);
|
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
if let Some(plugin_path) = plugin_path {
|
if let Some(plugin_path) = plugin_path {
|
||||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
let plugin_filename = plugin_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -29,19 +38,32 @@ pub fn read_plugin_file(
|
|||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
&plugin_filename,
|
&plugin_filename,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
pub fn add_plugin_file(
|
||||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
engine_state: &mut EngineState,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
|
storage_path: &str,
|
||||||
|
) {
|
||||||
|
if let Some(plugin_file) = plugin_file {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
match canonicalize_with(&plugin_file.item, cwd) {
|
||||||
|
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||||
|
Err(_) => {
|
||||||
|
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
@ -55,7 +77,7 @@ pub fn eval_config_contents(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) {
|
) {
|
||||||
if config_path.exists() & config_path.is_file() {
|
if config_path.exists() & config_path.is_file() {
|
||||||
let config_filename = config_path.to_string_lossy().to_owned();
|
let config_filename = config_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&config_path) {
|
if let Ok(contents) = std::fs::read(&config_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -63,15 +85,13 @@ pub fn eval_config_contents(
|
|||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
&config_filename,
|
&config_filename,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
if let Err(e) =
|
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
@ -84,3 +104,14 @@ pub fn eval_config_contents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
|
||||||
|
nu_path::config_dir().map(|mut history_path| {
|
||||||
|
history_path.push(storage_path);
|
||||||
|
history_path.push(match mode {
|
||||||
|
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
|
||||||
|
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
||||||
|
});
|
||||||
|
history_path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -2,12 +2,13 @@ use crate::util::{eval_source, report_error};
|
|||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_engine::convert_env_values;
|
use nu_engine::{convert_env_values, current_dir};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Value,
|
Config, PipelineData, ShellError, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::stdout_write_all_and_flush;
|
use nu_utils::stdout_write_all_and_flush;
|
||||||
|
|
||||||
@ -18,7 +19,6 @@ pub fn evaluate_file(
|
|||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
@ -27,35 +27,96 @@ pub fn evaluate_file(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = std::fs::read(&path).into_diagnostic()?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
|
|
||||||
|
let file_path = {
|
||||||
|
match canonicalize_with(&path, &cwd) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::FileNotFoundCustom(
|
||||||
|
format!("Could not access file '{}': {:?}", path, e.to_string()),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_path_str = match file_path.to_str() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::NonUtf8Custom(
|
||||||
|
format!(
|
||||||
|
"Input file name '{}' is not valid UTF8",
|
||||||
|
file_path.to_string_lossy()
|
||||||
|
),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = match std::fs::read(&file_path).into_diagnostic() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::FileNotFoundCustom(
|
||||||
|
format!(
|
||||||
|
"Could not read file '{}': {:?}",
|
||||||
|
file_path_str,
|
||||||
|
e.to_string()
|
||||||
|
),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
engine_state.start_in_file(Some(file_path_str));
|
||||||
|
|
||||||
|
let mut parent = file_path.clone();
|
||||||
|
parent.pop();
|
||||||
|
|
||||||
|
stack.add_env_var(
|
||||||
|
"FILE_PWD".to_string(),
|
||||||
|
Value::string(parent.to_string_lossy(), Span::unknown()),
|
||||||
|
);
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
trace!("parsing file: {}", path);
|
trace!("parsing file: {}", file_path_str);
|
||||||
|
let _ = parse(&mut working_set, Some(file_path_str), &file, false, &[]);
|
||||||
|
|
||||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||||
|
|
||||||
if working_set.find_decl(b"main").is_some() {
|
|
||||||
let args = format!("main {}", args.join(" "));
|
let args = format!("main {}", args.join(" "));
|
||||||
|
|
||||||
if !eval_source(
|
if !eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&file,
|
&file,
|
||||||
&path,
|
file_path_str,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
) {
|
) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
} else if !eval_source(engine_state, stack, &file, file_path_str, input) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -65,7 +126,7 @@ pub fn print_table_or_error(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
mut pipeline_data: PipelineData,
|
mut pipeline_data: PipelineData,
|
||||||
config: &mut Config,
|
config: &mut Config,
|
||||||
) {
|
) -> Option<i64> {
|
||||||
let exit_code = match &mut pipeline_data {
|
let exit_code = match &mut pipeline_data {
|
||||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -74,61 +135,73 @@ pub fn print_table_or_error(
|
|||||||
// Change the engine_state config to use the passed in configuration
|
// Change the engine_state config to use the passed in configuration
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config);
|
||||||
|
|
||||||
|
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, error);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Some(decl_id) => {
|
Some(decl_id) => {
|
||||||
let table = engine_state.get_decl(decl_id).run(
|
let command = engine_state.get_decl(decl_id);
|
||||||
engine_state,
|
if command.get_block_id().is_some() {
|
||||||
stack,
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
&Call::new(Span::new(0, 0)),
|
} else {
|
||||||
pipeline_data,
|
let table = command.run(
|
||||||
);
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&Call::new(Span::new(0, 0)),
|
||||||
|
pipeline_data,
|
||||||
|
);
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
for item in table {
|
print_or_exit(table, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
}
|
||||||
}
|
Err(error) => {
|
||||||
Err(error) => {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
for item in pipeline_data {
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure everything has finished
|
// Make sure everything has finished
|
||||||
if let Some(exit_code) = exit_code {
|
if let Some(exit_code) = exit_code {
|
||||||
let _: Vec<_> = exit_code.into_iter().collect();
|
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
|
||||||
|
exit_code
|
||||||
|
.pop()
|
||||||
|
.and_then(|last_exit_code| match last_exit_code {
|
||||||
|
Value::Int { val: code, .. } => Some(code),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||||
|
for item in pipeline_data {
|
||||||
|
if let Value::Error { error } = item {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = item.into_string("\n", config);
|
||||||
|
out.push('\n');
|
||||||
|
|
||||||
|
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
|
|||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
pub use repl::evaluate_repl;
|
pub use repl::evaluate_repl;
|
||||||
|
pub use repl::{eval_env_change_hook, eval_hook};
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
nu_ansi_term::{ansi::RESET, Style},
|
nu_ansi_term::{ansi::RESET, Style},
|
||||||
reedline::{
|
reedline::{
|
||||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||||
Painter, Suggestion,
|
Painter, Suggestion, UndoBehavior,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ impl DescriptionMenu {
|
|||||||
let description = self
|
let description = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.skip(self.skipped_rows)
|
.skip(self.skipped_rows)
|
||||||
.take(self.working_details.description_rows)
|
.take(self.working_details.description_rows)
|
||||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
|||||||
fn can_partially_complete(
|
fn can_partially_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
_values_updated: bool,
|
_values_updated: bool,
|
||||||
_line_buffer: &mut LineBuffer,
|
_editor: &mut Editor,
|
||||||
_completer: &mut dyn Completer,
|
_completer: &mut dyn Completer,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates menu values
|
/// Updates menu values
|
||||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
||||||
if self.only_buffer_difference {
|
if self.only_buffer_difference {
|
||||||
if let Some(old_string) = &self.input {
|
if let Some(old_string) = &self.input {
|
||||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.values = completer.complete(input, start);
|
self.values = completer.complete(input, start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||||
self.values =
|
self.values = completer.complete(
|
||||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
trimmed_buffer.as_str(),
|
||||||
|
editor.line_buffer().insertion_point(),
|
||||||
|
);
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
|||||||
/// collected from the completer
|
/// collected from the completer
|
||||||
fn update_working_details(
|
fn update_working_details(
|
||||||
&mut self,
|
&mut self,
|
||||||
line_buffer: &mut LineBuffer,
|
editor: &mut Editor,
|
||||||
completer: &mut dyn Completer,
|
completer: &mut dyn Completer,
|
||||||
painter: &Painter,
|
painter: &Painter,
|
||||||
) {
|
) {
|
||||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
|||||||
match event {
|
match event {
|
||||||
MenuEvent::Activate(_) => {
|
MenuEvent::Activate(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.input = Some(line_buffer.get_buffer().to_string());
|
self.input = Some(editor.get_buffer().to_string());
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
}
|
}
|
||||||
MenuEvent::Deactivate => self.active = false,
|
MenuEvent::Deactivate => self.active = false,
|
||||||
MenuEvent::Edit(_) => {
|
MenuEvent::Edit(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
self.update_examples()
|
self.update_examples()
|
||||||
}
|
}
|
||||||
MenuEvent::NextElement => {
|
MenuEvent::NextElement => {
|
||||||
@ -608,7 +610,7 @@ impl Menu for DescriptionMenu {
|
|||||||
let description_rows = self
|
let description_rows = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The buffer gets replaced in the Span location
|
/// The buffer gets replaced in the Span location
|
||||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
fn replace_in_buffer(&self, editor: &mut Editor) {
|
||||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||||
let start = span.start.min(line_buffer.len());
|
let start = span.start.min(editor.line_buffer().len());
|
||||||
let end = span.end.min(line_buffer.len());
|
let end = span.end.min(editor.line_buffer().len());
|
||||||
|
|
||||||
let string_len = if let Some(example_index) = self.example_index {
|
let replacement = if let Some(example_index) = self.example_index {
|
||||||
let example = self
|
self.examples
|
||||||
.examples
|
|
||||||
.get(example_index)
|
.get(example_index)
|
||||||
.expect("the example index is always checked");
|
.expect("the example index is always checked")
|
||||||
|
|
||||||
line_buffer.replace(start..end, example);
|
|
||||||
example.len()
|
|
||||||
} else {
|
} else {
|
||||||
line_buffer.replace(start..end, &value);
|
&value
|
||||||
value.len()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = line_buffer.insertion_point();
|
editor.edit_buffer(
|
||||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
|lb| {
|
||||||
line_buffer.set_insertion_point(offset);
|
lb.replace_range(start..end, replacement);
|
||||||
|
let mut offset = lb.insertion_point();
|
||||||
|
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||||
|
lb.set_insertion_point(offset);
|
||||||
|
},
|
||||||
|
UndoBehavior::CreateUndoPoint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use nu_engine::documentation::get_flags_section;
|
use nu_engine::documentation::get_flags_section;
|
||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||||
@ -16,9 +17,13 @@ impl NuHelpCompleter {
|
|||||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||||
let mut commands = full_commands
|
let mut commands = full_commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(sig, _, _, _)| {
|
.filter(|(sig, _, _, _, _)| {
|
||||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||||
|
|| sig
|
||||||
|
.search_terms
|
||||||
|
.iter()
|
||||||
|
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
||||||
|| sig
|
|| sig
|
||||||
.extra_usage
|
.extra_usage
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
@ -26,7 +31,7 @@ impl NuHelpCompleter {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
commands.sort_by(|(a, _, _, _), (b, _, _, _)| {
|
commands.sort_by(|(a, _, _, _, _), (b, _, _, _, _)| {
|
||||||
let a_distance = levenshtein_distance(line, &a.name);
|
let a_distance = levenshtein_distance(line, &a.name);
|
||||||
let b_distance = levenshtein_distance(line, &b.name);
|
let b_distance = levenshtein_distance(line, &b.name);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
@ -34,7 +39,7 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
commands
|
commands
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(sig, examples, _, _)| {
|
.map(|(sig, examples, _, _, _)| {
|
||||||
let mut long_desc = String::new();
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
let usage = &sig.usage;
|
let usage = &sig.usage;
|
||||||
@ -49,7 +54,7 @@ impl NuHelpCompleter {
|
|||||||
long_desc.push_str("\r\n\r\n");
|
long_desc.push_str("\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
long_desc.push_str(&format!("Usage:\r\n > {}\r\n", sig.call_signature()));
|
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(sig))
|
long_desc.push_str(&get_flags_section(sig))
|
||||||
@ -61,27 +66,28 @@ impl NuHelpCompleter {
|
|||||||
{
|
{
|
||||||
long_desc.push_str("\r\nParameters:\r\n");
|
long_desc.push_str("\r\nParameters:\r\n");
|
||||||
for positional in &sig.required_positional {
|
for positional in &sig.required_positional {
|
||||||
long_desc
|
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||||
.push_str(&format!(" {}: {}\r\n", positional.name, positional.desc));
|
|
||||||
}
|
}
|
||||||
for positional in &sig.optional_positional {
|
for positional in &sig.optional_positional {
|
||||||
long_desc.push_str(&format!(
|
let _ = write!(
|
||||||
|
long_desc,
|
||||||
" (optional) {}: {}\r\n",
|
" (optional) {}: {}\r\n",
|
||||||
positional.name, positional.desc
|
positional.name, positional.desc
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rest_positional) = &sig.rest_positional {
|
if let Some(rest_positional) = &sig.rest_positional {
|
||||||
long_desc.push_str(&format!(
|
let _ = write!(
|
||||||
|
long_desc,
|
||||||
" ...{}: {}\r\n",
|
" ...{}: {}\r\n",
|
||||||
rest_positional.name, rest_positional.desc
|
rest_positional.name, rest_positional.desc
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let extra: Vec<String> = examples
|
let extra: Vec<String> = examples
|
||||||
.iter()
|
.iter()
|
||||||
.map(|example| example.example.to_string())
|
.map(|example| example.example.replace('\n', "\r\n"))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Suggestion {
|
Suggestion {
|
||||||
|
@ -42,20 +42,14 @@ impl Completer for NuMenuCompleter {
|
|||||||
|
|
||||||
if let Some(buffer) = block.signature.get_positional(0) {
|
if let Some(buffer) = block.signature.get_positional(0) {
|
||||||
if let Some(buffer_id) = &buffer.var_id {
|
if let Some(buffer_id) = &buffer.var_id {
|
||||||
let line_buffer = Value::String {
|
let line_buffer = Value::string(parsed.remainder, self.span);
|
||||||
val: parsed.remainder.to_string(),
|
|
||||||
span: self.span,
|
|
||||||
};
|
|
||||||
self.stack.add_var(*buffer_id, line_buffer);
|
self.stack.add_var(*buffer_id, line_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(position) = block.signature.get_positional(1) {
|
if let Some(position) = block.signature.get_positional(1) {
|
||||||
if let Some(position_id) = &position.var_id {
|
if let Some(position_id) = &position.var_id {
|
||||||
let line_buffer = Value::Int {
|
let line_buffer = Value::int(pos as i64, self.span);
|
||||||
val: pos as i64,
|
|
||||||
span: self.span,
|
|
||||||
};
|
|
||||||
self.stack.add_var(*position_id, line_buffer);
|
self.stack.add_var(*position_id, line_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
||||||
use reedline::Highlighter;
|
use reedline::Highlighter;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -12,13 +12,19 @@ impl Command for NuHighlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("nu-highlight").category(Category::Strings)
|
Signature::build("nu-highlight")
|
||||||
|
.category(Category::Strings)
|
||||||
|
.input_output_types(vec![(Type::String, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Syntax highlight the input string."
|
"Syntax highlight the input string."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["syntax", "color", "convert"]
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -2,7 +2,8 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,17 +16,30 @@ impl Command for Print {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("print")
|
Signature::build("print")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||||
.switch(
|
.switch(
|
||||||
"no_newline",
|
"no-newline",
|
||||||
"print without inserting a newline for the line ending",
|
"print without inserting a newline for the line ending",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
|
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Prints the values given"
|
"Print the given values to stdout"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||||
|
Since this command has no output, there is no point in piping it with other commands.
|
||||||
|
|
||||||
|
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["display"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -36,15 +50,15 @@ impl Command for Print {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag("no_newline");
|
let no_newline = call.has_flag("no-newline");
|
||||||
let head = call.head;
|
let to_stderr = call.has_flag("stderr");
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg.into_pipeline_data()
|
arg.into_pipeline_data()
|
||||||
.print(engine_state, stack, no_newline)?;
|
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::new(head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::DefaultPrompt;
|
use reedline::DefaultPrompt;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
reedline::{
|
reedline::{
|
||||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||||
@ -16,6 +17,7 @@ pub struct NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: Option<String>,
|
default_vi_insert_prompt_indicator: Option<String>,
|
||||||
default_vi_normal_prompt_indicator: Option<String>,
|
default_vi_normal_prompt_indicator: Option<String>,
|
||||||
default_multiline_indicator: Option<String>,
|
default_multiline_indicator: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NushellPrompt {
|
impl Default for NushellPrompt {
|
||||||
@ -33,6 +35,7 @@ impl NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: None,
|
default_vi_insert_prompt_indicator: None,
|
||||||
default_vi_normal_prompt_indicator: None,
|
default_vi_normal_prompt_indicator: None,
|
||||||
default_multiline_indicator: None,
|
default_multiline_indicator: None,
|
||||||
|
render_right_prompt_on_last_line: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +43,13 @@ impl NushellPrompt {
|
|||||||
self.left_prompt_string = prompt_string;
|
self.left_prompt_string = prompt_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
pub fn update_prompt_right(
|
||||||
|
&mut self,
|
||||||
|
prompt_string: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
|
) {
|
||||||
self.right_prompt_string = prompt_string;
|
self.right_prompt_string = prompt_string;
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||||
@ -67,6 +75,7 @@ impl NushellPrompt {
|
|||||||
prompt_indicator_string: Option<String>,
|
prompt_indicator_string: Option<String>,
|
||||||
prompt_multiline_indicator_string: Option<String>,
|
prompt_multiline_indicator_string: Option<String>,
|
||||||
prompt_vi: (Option<String>, Option<String>),
|
prompt_vi: (Option<String>, Option<String>),
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
) {
|
) {
|
||||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||||
|
|
||||||
@ -77,6 +86,8 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||||
|
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||||
@ -86,6 +97,11 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
impl Prompt for NushellPrompt {
|
impl Prompt for NushellPrompt {
|
||||||
fn render_prompt_left(&self) -> Cow<str> {
|
fn render_prompt_left(&self) -> Cow<str> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = enable_vt_processing();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(prompt_string) = &self.left_prompt_string {
|
if let Some(prompt_string) = &self.left_prompt_string {
|
||||||
prompt_string.replace('\n', "\r\n").into()
|
prompt_string.replace('\n', "\r\n").into()
|
||||||
} else {
|
} else {
|
||||||
@ -156,4 +172,8 @@ impl Prompt for NushellPrompt {
|
|||||||
prefix, history_search.term
|
prefix, history_search.term
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn right_prompt_on_last_line(&self) -> bool {
|
||||||
|
self.render_right_prompt_on_last_line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::util::report_error;
|
use crate::util::report_error;
|
||||||
use crate::NushellPrompt;
|
use crate::NushellPrompt;
|
||||||
use log::info;
|
use log::trace;
|
||||||
use nu_engine::eval_subexpression;
|
use nu_engine::eval_subexpression;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Value,
|
Config, PipelineData, Value,
|
||||||
};
|
};
|
||||||
use reedline::Prompt;
|
use reedline::Prompt;
|
||||||
|
|
||||||
@ -15,18 +15,21 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
|||||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||||
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
|
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||||
|
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||||
|
|
||||||
fn get_prompt_string(
|
fn get_prompt_string(
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val: block_id,
|
val: block_id,
|
||||||
captures,
|
captures,
|
||||||
..
|
..
|
||||||
@ -34,20 +37,34 @@ fn get_prompt_string(
|
|||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
let mut stack = stack.captures_to_stack(&captures);
|
let mut stack = stack.captures_to_stack(&captures);
|
||||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||||
let ret_val = eval_subexpression(
|
let ret_val =
|
||||||
engine_state,
|
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
||||||
&mut stack,
|
trace!(
|
||||||
block,
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
);
|
);
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
match ret_val {
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
Ok(ret_val) => Some(ret_val),
|
||||||
file!(),
|
Err(err) => {
|
||||||
line!(),
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
column!()
|
report_error(&working_set, &err);
|
||||||
);
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Value::Block { val: block_id, .. } => {
|
||||||
|
let block = engine_state.get_block(block_id);
|
||||||
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||||
|
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
||||||
|
trace!(
|
||||||
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
);
|
||||||
|
|
||||||
match ret_val {
|
match ret_val {
|
||||||
Ok(ret_val) => Some(ret_val),
|
Ok(ret_val) => Some(ret_val),
|
||||||
@ -86,57 +103,39 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
nu_prompt: &'prompt mut NushellPrompt,
|
nu_prompt: &'prompt mut NushellPrompt,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> &'prompt dyn Prompt {
|
) -> &'prompt dyn Prompt {
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
||||||
PROMPT_COMMAND,
|
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let right_prompt_string = get_prompt_string(
|
// Now that we have the prompt string lets ansify it.
|
||||||
PROMPT_COMMAND_RIGHT,
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
config,
|
let left_prompt_string = if config.shell_integration {
|
||||||
engine_state,
|
match left_prompt_string {
|
||||||
&mut stack,
|
Some(prompt_string) => Some(format!(
|
||||||
is_perf_true,
|
"{}{}{}",
|
||||||
);
|
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||||
|
)),
|
||||||
|
None => left_prompt_string,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
left_prompt_string
|
||||||
|
};
|
||||||
|
|
||||||
let prompt_indicator_string = get_prompt_string(
|
let right_prompt_string =
|
||||||
PROMPT_INDICATOR,
|
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_multiline_string = get_prompt_string(
|
let prompt_indicator_string =
|
||||||
PROMPT_MULTILINE_INDICATOR,
|
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_insert_string = get_prompt_string(
|
let prompt_multiline_string =
|
||||||
PROMPT_INDICATOR_VI_INSERT,
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_normal_string = get_prompt_string(
|
let prompt_vi_insert_string =
|
||||||
PROMPT_INDICATOR_VI_NORMAL,
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
let prompt_vi_normal_string =
|
||||||
&mut stack,
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
@ -145,12 +144,11 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
prompt_indicator_string,
|
prompt_indicator_string,
|
||||||
prompt_multiline_string,
|
prompt_multiline_string,
|
||||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||||
|
config.render_right_prompt_on_last_line,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
let ret_val = nu_prompt as &dyn Prompt;
|
||||||
if is_perf_true {
|
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
use super::DescriptionMenu;
|
use super::DescriptionMenu;
|
||||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use nu_color_config::lookup_ansi_color_style;
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
color_value_string, create_menus,
|
create_menus,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
|
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value,
|
||||||
ShellError, Span, Value,
|
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||||
@ -110,11 +109,11 @@ pub(crate) fn add_menus(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut temp_stack = Stack::new();
|
let mut temp_stack = Stack::new();
|
||||||
let input = Value::nothing(Span::test_data()).into_pipeline_data();
|
let input = PipelineData::Empty;
|
||||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
for menu in create_menus(&value, config)? {
|
for menu in create_menus(&value)? {
|
||||||
line_editor =
|
line_editor =
|
||||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||||
}
|
}
|
||||||
@ -159,14 +158,11 @@ macro_rules! add_style {
|
|||||||
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||||
$menu = match extract_value($name, $cols, $vals, $span) {
|
$menu = match extract_value($name, $cols, $vals, $span) {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let text = match text {
|
let style = match text {
|
||||||
Value::String { val, .. } => val.clone(),
|
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||||
Value::Record { cols, vals, span } => {
|
Value::Record { .. } => color_record_to_nustyle(&text),
|
||||||
color_value_string(span, cols, vals, $config).into_string("", $config)
|
_ => lookup_ansi_color_style("green"),
|
||||||
}
|
|
||||||
_ => "green".to_string(),
|
|
||||||
};
|
};
|
||||||
let style = lookup_ansi_color_style(&text);
|
|
||||||
$f($menu, style)
|
$f($menu, style)
|
||||||
}
|
}
|
||||||
Err(_) => $menu,
|
Err(_) => $menu,
|
||||||
@ -251,7 +247,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -337,7 +333,7 @@ pub(crate) fn add_list_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -459,7 +455,7 @@ pub(crate) fn add_description_menu(
|
|||||||
completer,
|
completer,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -477,7 +473,7 @@ pub(crate) fn add_description_menu(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or omitted value".to_string(),
|
"closure or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span()?,
|
menu.source.span()?,
|
||||||
)),
|
)),
|
||||||
@ -491,7 +487,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|||||||
KeyCode::Tab,
|
KeyCode::Tab,
|
||||||
ReedlineEvent::UntilFound(vec![
|
ReedlineEvent::UntilFound(vec![
|
||||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||||
ReedlineEvent::MenuNext,
|
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -666,6 +662,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
}
|
}
|
||||||
|
"space" => KeyCode::Char(' '),
|
||||||
"down" => KeyCode::Down,
|
"down" => KeyCode::Down,
|
||||||
"up" => KeyCode::Up,
|
"up" => KeyCode::Up,
|
||||||
"left" => KeyCode::Left,
|
"left" => KeyCode::Left,
|
||||||
@ -682,9 +679,9 @@ fn add_parsed_keybinding(
|
|||||||
let fn_num: u8 = c[1..]
|
let fn_num: u8 = c[1..]
|
||||||
.parse()
|
.parse()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|num| matches!(num, 1..=12))
|
.filter(|num| matches!(num, 1..=20))
|
||||||
.ok_or(ShellError::UnsupportedConfigValue(
|
.ok_or(ShellError::UnsupportedConfigValue(
|
||||||
"(f1|f2|...|f12)".to_string(),
|
"(f1|f2|...|f20)".to_string(),
|
||||||
format!("unknown function key: {}", c),
|
format!("unknown function key: {}", c),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span()?,
|
||||||
))?;
|
))?;
|
||||||
@ -814,7 +811,6 @@ fn event_from_record(
|
|||||||
) -> Result<ReedlineEvent, ShellError> {
|
) -> Result<ReedlineEvent, ShellError> {
|
||||||
let event = match name {
|
let event = match name {
|
||||||
"none" => ReedlineEvent::None,
|
"none" => ReedlineEvent::None,
|
||||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
|
||||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||||
@ -822,6 +818,8 @@ fn event_from_record(
|
|||||||
"ctrld" => ReedlineEvent::CtrlD,
|
"ctrld" => ReedlineEvent::CtrlD,
|
||||||
"ctrlc" => ReedlineEvent::CtrlC,
|
"ctrlc" => ReedlineEvent::CtrlC,
|
||||||
"enter" => ReedlineEvent::Enter,
|
"enter" => ReedlineEvent::Enter,
|
||||||
|
"submit" => ReedlineEvent::Submit,
|
||||||
|
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||||
"esc" | "escape" => ReedlineEvent::Esc,
|
"esc" | "escape" => ReedlineEvent::Esc,
|
||||||
"up" => ReedlineEvent::Up,
|
"up" => ReedlineEvent::Up,
|
||||||
"down" => ReedlineEvent::Down,
|
"down" => ReedlineEvent::Down,
|
||||||
@ -875,7 +873,16 @@ fn edit_from_record(
|
|||||||
"moveleft" => EditCommand::MoveLeft,
|
"moveleft" => EditCommand::MoveLeft,
|
||||||
"moveright" => EditCommand::MoveRight,
|
"moveright" => EditCommand::MoveRight,
|
||||||
"movewordleft" => EditCommand::MoveWordLeft,
|
"movewordleft" => EditCommand::MoveWordLeft,
|
||||||
|
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
||||||
"movewordright" => EditCommand::MoveWordRight,
|
"movewordright" => EditCommand::MoveWordRight,
|
||||||
|
"movewordrightend" => EditCommand::MoveWordRightEnd,
|
||||||
|
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
|
||||||
|
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
||||||
|
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
||||||
|
"movetoposition" => {
|
||||||
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
|
EditCommand::MoveToPosition(value.as_integer()? as usize)
|
||||||
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
@ -888,6 +895,7 @@ fn edit_from_record(
|
|||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
"delete" => EditCommand::Delete,
|
"delete" => EditCommand::Delete,
|
||||||
|
"cutchar" => EditCommand::CutChar,
|
||||||
"backspaceword" => EditCommand::BackspaceWord,
|
"backspaceword" => EditCommand::BackspaceWord,
|
||||||
"deleteword" => EditCommand::DeleteWord,
|
"deleteword" => EditCommand::DeleteWord,
|
||||||
"clear" => EditCommand::Clear,
|
"clear" => EditCommand::Clear,
|
||||||
@ -898,7 +906,11 @@ fn edit_from_record(
|
|||||||
"cuttoend" => EditCommand::CutToEnd,
|
"cuttoend" => EditCommand::CutToEnd,
|
||||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||||
"cutwordleft" => EditCommand::CutWordLeft,
|
"cutwordleft" => EditCommand::CutWordLeft,
|
||||||
|
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||||
"cutwordright" => EditCommand::CutWordRight,
|
"cutwordright" => EditCommand::CutWordRight,
|
||||||
|
"cutbigwordright" => EditCommand::CutBigWordRight,
|
||||||
|
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
|
||||||
|
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
|
||||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||||
"uppercaseword" => EditCommand::UppercaseWord,
|
"uppercaseword" => EditCommand::UppercaseWord,
|
||||||
@ -948,6 +960,7 @@ fn edit_from_record(
|
|||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftBefore(char)
|
EditCommand::MoveLeftBefore(char)
|
||||||
}
|
}
|
||||||
|
"complete" => EditCommand::Complete,
|
||||||
e => {
|
e => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"reedline EditCommand".to_string(),
|
"reedline EditCommand".to_string(),
|
||||||
@ -976,10 +989,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_send_event() {
|
fn test_send_event() {
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||||
@ -999,10 +1009,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_edit_event() {
|
fn test_edit_event() {
|
||||||
let cols = vec!["edit".to_string()];
|
let cols = vec!["edit".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Clear")];
|
||||||
val: "Clear".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||||
@ -1026,14 +1033,8 @@ mod test {
|
|||||||
fn test_send_menu() {
|
fn test_send_menu() {
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
@ -1059,14 +1060,8 @@ mod test {
|
|||||||
// Menu event
|
// Menu event
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1077,10 +1072,7 @@ mod test {
|
|||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1121,14 +1113,8 @@ mod test {
|
|||||||
// Menu event
|
// Menu event
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1139,10 +1125,7 @@ mod test {
|
|||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1170,10 +1153,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_error() {
|
fn test_error() {
|
||||||
let cols = vec!["not_exist".to_string()];
|
let cols = vec!["not_exist".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::get_shape_color;
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
|
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
use nu_protocol::Config;
|
use nu_protocol::{Config, Span};
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
|
|
||||||
pub struct NuHighlighter {
|
pub struct NuHighlighter {
|
||||||
@ -15,10 +16,12 @@ impl Highlighter for NuHighlighter {
|
|||||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||||
trace!("highlighting: {}", line);
|
trace!("highlighting: {}", line);
|
||||||
|
|
||||||
let (shapes, global_span_offset) = {
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let block = {
|
||||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
let (shapes, global_span_offset) = {
|
||||||
let shapes = flatten_block(&working_set, &block);
|
let shapes = flatten_block(&working_set, &block);
|
||||||
(shapes, self.engine_state.next_span_start())
|
(shapes, self.engine_state.next_span_start())
|
||||||
};
|
};
|
||||||
@ -26,6 +29,15 @@ impl Highlighter for NuHighlighter {
|
|||||||
let mut output = StyledText::default();
|
let mut output = StyledText::default();
|
||||||
let mut last_seen_span = global_span_offset;
|
let mut last_seen_span = global_span_offset;
|
||||||
|
|
||||||
|
let global_cursor_offset = _cursor + global_span_offset;
|
||||||
|
let matching_brackets_pos = find_matching_brackets(
|
||||||
|
line,
|
||||||
|
&working_set,
|
||||||
|
&block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
);
|
||||||
|
|
||||||
for shape in &shapes {
|
for shape in &shapes {
|
||||||
if shape.0.end <= last_seen_span
|
if shape.0.end <= last_seen_span
|
||||||
|| last_seen_span < global_span_offset
|
|| last_seen_span < global_span_offset
|
||||||
@ -44,166 +56,75 @@ impl Highlighter for NuHighlighter {
|
|||||||
let next_token = line
|
let next_token = line
|
||||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
macro_rules! add_colored_token_with_bracket_highlight {
|
||||||
|
($shape:expr, $span:expr, $text:expr) => {{
|
||||||
|
let spans = split_span_by_highlight_positions(
|
||||||
|
line,
|
||||||
|
&$span,
|
||||||
|
&matching_brackets_pos,
|
||||||
|
global_span_offset,
|
||||||
|
);
|
||||||
|
spans.iter().for_each(|(part, highlight)| {
|
||||||
|
let start = part.start - $span.start;
|
||||||
|
let end = part.end - $span.start;
|
||||||
|
let text = (&next_token[start..end]).to_string();
|
||||||
|
let mut style = get_shape_color($shape.to_string(), &self.config);
|
||||||
|
if *highlight {
|
||||||
|
style = get_matching_brackets_style(style, &self.config);
|
||||||
|
}
|
||||||
|
output.push((style, text));
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_colored_token {
|
||||||
|
($shape:expr, $text:expr) => {
|
||||||
|
output.push((get_shape_color($shape.to_string(), &self.config), $text))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match shape.1 {
|
match shape.1 {
|
||||||
FlatShape::Garbage => output.push((
|
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Garbage
|
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Binary => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::Bool => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Int => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Nothing => output.push((
|
FlatShape::Float => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Nothing
|
FlatShape::Range => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::External => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Binary => {
|
FlatShape::Literal => add_colored_token!(shape.1, next_token),
|
||||||
// nushell ?
|
FlatShape::Operator => add_colored_token!(shape.1, next_token),
|
||||||
output.push((
|
FlatShape::Signature => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::String => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
|
||||||
))
|
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
|
||||||
}
|
|
||||||
FlatShape::Bool => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Int => {
|
|
||||||
// nushell Int
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Float => {
|
|
||||||
// nushell Decimal
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Range => output.push((
|
|
||||||
// nushell DotDot ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::InternalCall => output.push((
|
|
||||||
// nushell InternalCommand
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::External => {
|
|
||||||
// nushell ExternalCommand
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::ExternalArg => {
|
|
||||||
// nushell ExternalWord
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Literal => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Operator => output.push((
|
|
||||||
// nushell Operator
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Signature => output.push((
|
|
||||||
// nushell ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::String => {
|
|
||||||
// nushell String
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::StringInterpolation => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::DateTime => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::List => {
|
FlatShape::List => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Table => {
|
FlatShape::Table => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Record => {
|
FlatShape::Record => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatShape::Block => {
|
FlatShape::Block => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Filepath => output.push((
|
|
||||||
// nushell Path
|
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Directory => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Directory => output.push((
|
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Directory
|
FlatShape::Pipe => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::And => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::Or => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Redirection => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::GlobPattern => output.push((
|
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||||
// nushell GlobPattern
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Variable => output.push((
|
|
||||||
// nushell Variable
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Flag => {
|
|
||||||
// nushell Flag
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Custom(..) => output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
last_seen_span = shape.0.end;
|
last_seen_span = shape.0.end;
|
||||||
}
|
}
|
||||||
@ -216,3 +137,300 @@ impl Highlighter for NuHighlighter {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_span_by_highlight_positions(
|
||||||
|
line: &str,
|
||||||
|
span: &Span,
|
||||||
|
highlight_positions: &Vec<usize>,
|
||||||
|
global_span_offset: usize,
|
||||||
|
) -> Vec<(Span, bool)> {
|
||||||
|
let mut start = span.start;
|
||||||
|
let mut result: Vec<(Span, bool)> = Vec::new();
|
||||||
|
for pos in highlight_positions {
|
||||||
|
if start <= *pos && pos < &span.end {
|
||||||
|
if start < *pos {
|
||||||
|
result.push((Span::new(start, *pos), false));
|
||||||
|
}
|
||||||
|
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
||||||
|
let end = span_str
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.map(|c| pos + get_char_length(c))
|
||||||
|
.unwrap_or(pos + 1);
|
||||||
|
result.push((Span::new(*pos, end), true));
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < span.end {
|
||||||
|
result.push((Span::new(start, span.end), false));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_brackets(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Vec<usize> {
|
||||||
|
const BRACKETS: &str = "{}[]()";
|
||||||
|
|
||||||
|
// calculate first bracket position
|
||||||
|
let global_end_offset = line.len() + global_span_offset;
|
||||||
|
let global_bracket_pos =
|
||||||
|
if global_cursor_offset == global_end_offset && global_end_offset > global_span_offset {
|
||||||
|
// cursor is at the end of a non-empty string -- find block end at the previous position
|
||||||
|
if let Some(last_char) = line.chars().last() {
|
||||||
|
global_cursor_offset - get_char_length(last_char)
|
||||||
|
} else {
|
||||||
|
global_cursor_offset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// cursor is in the middle of a string -- find block end at the current position
|
||||||
|
global_cursor_offset
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that position contains bracket
|
||||||
|
let match_idx = global_bracket_pos - global_span_offset;
|
||||||
|
if match_idx >= line.len()
|
||||||
|
|| !BRACKETS.contains(get_char_at_index(line, match_idx).unwrap_or_default())
|
||||||
|
{
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// find matching bracket by finding matching block end
|
||||||
|
let matching_block_end = find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
block,
|
||||||
|
global_span_offset,
|
||||||
|
global_bracket_pos,
|
||||||
|
);
|
||||||
|
if let Some(pos) = matching_block_end {
|
||||||
|
let matching_idx = pos - global_span_offset;
|
||||||
|
if BRACKETS.contains(get_char_at_index(line, matching_idx).unwrap_or_default()) {
|
||||||
|
return if global_bracket_pos < pos {
|
||||||
|
vec![global_bracket_pos, pos]
|
||||||
|
} else {
|
||||||
|
vec![pos, global_bracket_pos]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_block(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
for p in &block.pipelines {
|
||||||
|
for e in &p.elements {
|
||||||
|
match e {
|
||||||
|
PipelineElement::Expression(_, e)
|
||||||
|
| PipelineElement::Redirection(_, _, e)
|
||||||
|
| PipelineElement::And(_, e)
|
||||||
|
| PipelineElement::Or(_, e) => {
|
||||||
|
if e.span.contains(global_cursor_offset) {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
e,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_expr(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expression: &Expression,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
macro_rules! find_in_expr_or_continue {
|
||||||
|
($inner_expr:ident) => {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
$inner_expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
|
||||||
|
{
|
||||||
|
let expr_first = expression.span.start;
|
||||||
|
let span_str = &line
|
||||||
|
[expression.span.start - global_span_offset..expression.span.end - global_span_offset];
|
||||||
|
let expr_last = span_str
|
||||||
|
.chars()
|
||||||
|
.last()
|
||||||
|
.map(|c| expression.span.end - get_char_length(c))
|
||||||
|
.unwrap_or(expression.span.start);
|
||||||
|
|
||||||
|
return match &expression.expr {
|
||||||
|
Expr::Bool(_) => None,
|
||||||
|
Expr::Int(_) => None,
|
||||||
|
Expr::Float(_) => None,
|
||||||
|
Expr::Binary(_) => None,
|
||||||
|
Expr::Range(..) => None,
|
||||||
|
Expr::Var(_) => None,
|
||||||
|
Expr::VarDecl(_) => None,
|
||||||
|
Expr::ExternalCall(..) => None,
|
||||||
|
Expr::Operator(_) => None,
|
||||||
|
Expr::UnaryNot(_) => None,
|
||||||
|
Expr::Keyword(..) => None,
|
||||||
|
Expr::ValueWithUnit(..) => None,
|
||||||
|
Expr::DateTime(_) => None,
|
||||||
|
Expr::Filepath(_) => None,
|
||||||
|
Expr::Directory(_) => None,
|
||||||
|
Expr::GlobPattern(_) => None,
|
||||||
|
Expr::String(_) => None,
|
||||||
|
Expr::CellPath(_) => None,
|
||||||
|
Expr::ImportPattern(_) => None,
|
||||||
|
Expr::Overlay(_) => None,
|
||||||
|
Expr::Signature(_) => None,
|
||||||
|
Expr::Nothing => None,
|
||||||
|
Expr::Garbage => None,
|
||||||
|
|
||||||
|
Expr::Table(hdr, rows) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at table end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at table start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside table
|
||||||
|
for inner_expr in hdr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
for row in rows {
|
||||||
|
for inner_expr in row {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Record(exprs) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at record end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at record start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside record
|
||||||
|
for (k, v) in exprs {
|
||||||
|
find_in_expr_or_continue!(k);
|
||||||
|
find_in_expr_or_continue!(v);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Call(call) => {
|
||||||
|
for arg in &call.arguments {
|
||||||
|
let opt_expr = match arg {
|
||||||
|
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||||
|
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||||
|
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(inner_expr) = opt_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
&b.head,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
),
|
||||||
|
|
||||||
|
Expr::BinaryOp(lhs, op, rhs) => {
|
||||||
|
find_in_expr_or_continue!(lhs);
|
||||||
|
find_in_expr_or_continue!(op);
|
||||||
|
find_in_expr_or_continue!(rhs);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Block(block_id)
|
||||||
|
| Expr::Closure(block_id)
|
||||||
|
| Expr::RowCondition(block_id)
|
||||||
|
| Expr::Subexpression(block_id) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at block end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at block start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside block
|
||||||
|
let nested_block = working_set.get_block(*block_id);
|
||||||
|
find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
nested_block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::StringInterpolation(inner_expr) => {
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::List(inner_expr) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at list end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at list start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside list
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_at_index(s: &str, index: usize) -> Option<char> {
|
||||||
|
s[index..].chars().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_length(c: char) -> usize {
|
||||||
|
c.to_string().len()
|
||||||
|
}
|
||||||
|
@ -1,26 +1,43 @@
|
|||||||
use log::trace;
|
use crate::repl::eval_hook;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::CliError;
|
use nu_protocol::CliError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, ShellError, Span, Value,
|
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
//
|
//
|
||||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||||
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
||||||
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||||
pub fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
//
|
||||||
gather_env_vars(std::env::vars(), engine_state);
|
// The "PWD" env value will be forced to `init_cwd`.
|
||||||
|
// The reason to use `init_cwd`:
|
||||||
|
//
|
||||||
|
// While gathering parent env vars, the parent `PWD` may not be the same as `current working directory`.
|
||||||
|
// Consider to the following command as the case (assume we execute command inside `/tmp`):
|
||||||
|
//
|
||||||
|
// tmux split-window -v -c "#{pane_current_path}"
|
||||||
|
//
|
||||||
|
// Here nu execute external command `tmux`, and tmux starts a new `nushell`, with `init_cwd` value "#{pane_current_path}".
|
||||||
|
// But at the same time `PWD` still remains to be `/tmp`.
|
||||||
|
//
|
||||||
|
// In this scenario, the new `nushell`'s PWD should be "#{pane_current_path}" rather init_cwd.
|
||||||
|
pub fn gather_parent_env_vars(engine_state: &mut EngineState, init_cwd: &Path) {
|
||||||
|
gather_env_vars(std::env::vars(), engine_state, init_cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &mut EngineState) {
|
fn gather_env_vars(
|
||||||
|
vars: impl Iterator<Item = (String, String)>,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
init_cwd: &Path,
|
||||||
|
) {
|
||||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
@ -43,35 +60,31 @@ fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut fake_env_file = String::new();
|
let mut fake_env_file = String::new();
|
||||||
let mut has_pwd = false;
|
|
||||||
|
|
||||||
// Write all the env vars into a fake file
|
// Write all the env vars into a fake file
|
||||||
for (name, val) in vars {
|
for (name, val) in vars {
|
||||||
if name == "PWD" {
|
|
||||||
has_pwd = true;
|
|
||||||
}
|
|
||||||
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_pwd {
|
match init_cwd.to_str() {
|
||||||
match std::env::current_dir() {
|
Some(cwd) => {
|
||||||
Ok(cwd) => {
|
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
|
||||||
put_env_to_fake_file("PWD", &cwd.to_string_lossy(), &mut fake_env_file);
|
}
|
||||||
}
|
None => {
|
||||||
Err(e) => {
|
// Could not capture current working directory
|
||||||
// Could not capture current working directory
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
report_error(
|
||||||
report_error(
|
&working_set,
|
||||||
&working_set,
|
&ShellError::GenericError(
|
||||||
&ShellError::GenericError(
|
"Current directory is not a valid utf-8 path".to_string(),
|
||||||
"Current directory not found".to_string(),
|
"".to_string(),
|
||||||
"".to_string(),
|
None,
|
||||||
None,
|
Some(format!(
|
||||||
Some(format!("Retrieving current directory failed: {:?}", e)),
|
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||||
Vec::new(),
|
init_cwd
|
||||||
),
|
)),
|
||||||
);
|
Vec::new(),
|
||||||
}
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,8 +204,6 @@ pub fn eval_source(
|
|||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
trace!("eval_source");
|
|
||||||
|
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let (output, err) = parse(
|
let (output, err) = parse(
|
||||||
@ -211,35 +222,48 @@ pub fn eval_source(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
Ok(p) => p,
|
set_last_exit_code(stack, 1);
|
||||||
Err(e) => {
|
report_error_new(engine_state, &err);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
return false;
|
||||||
report_error(&working_set, &e);
|
}
|
||||||
get_init_cwd()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(mut pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
let config = engine_state.get_config();
|
||||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
let result;
|
||||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
if let PipelineData::ExternalStream {
|
||||||
} else {
|
stdout: stream,
|
||||||
set_last_exit_code(stack, 0);
|
stderr: stderr_stream,
|
||||||
|
exit_code,
|
||||||
|
..
|
||||||
|
} = pipeline_data
|
||||||
|
{
|
||||||
|
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
||||||
|
} else if let Some(hook) = config.hooks.display_output.clone() {
|
||||||
|
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
|
||||||
|
Err(err) => {
|
||||||
|
result = Err(err);
|
||||||
|
}
|
||||||
|
Ok(val) => {
|
||||||
|
result = val.print(engine_state, stack, false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
set_last_exit_code(stack, 0);
|
result = pipeline_data.print(engine_state, stack, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = pipeline_data.print(engine_state, stack, false) {
|
match result {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
Err(err) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
Ok(exit_code) => {
|
||||||
|
set_last_exit_code(stack, exit_code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
@ -265,10 +289,7 @@ pub fn eval_source(
|
|||||||
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"LAST_EXIT_CODE".to_string(),
|
"LAST_EXIT_CODE".to_string(),
|
||||||
Value::Int {
|
Value::int(exit_code, Span::unknown()),
|
||||||
val: exit_code,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +305,15 @@ pub fn report_error(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_error_new(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||||
|
) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, error);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_init_cwd() -> PathBuf {
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
match std::env::current_dir() {
|
match std::env::current_dir() {
|
||||||
Ok(cwd) => cwd,
|
Ok(cwd) => cwd,
|
||||||
@ -297,6 +327,17 @@ pub fn get_init_cwd() -> PathBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||||
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
get_init_cwd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -314,6 +355,7 @@ mod test {
|
|||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
|
Path::new("t"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let env = engine_state.render_env_vars();
|
let env = engine_state.render_env_vars();
|
||||||
|
817
crates/nu-cli/tests/completions.rs
Normal file
817
crates/nu-cli/tests/completions.rs
Normal file
@ -0,0 +1,817 @@
|
|||||||
|
pub mod support;
|
||||||
|
|
||||||
|
use nu_cli::NuCompleter;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
|
use reedline::{Completer, Suggestion};
|
||||||
|
use rstest::{fixture, rstest};
|
||||||
|
use support::{completions_helpers::new_quote_engine, file, folder, match_suggestions, new_engine};
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = "def tst [--mod -s] {}";
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn completer_strings() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||||
|
def my-command [animal: string@animals] { print $animal }"#;
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn extern_completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = r#"
|
||||||
|
def animals [] { [ "cat", "dog", "eel" ] }
|
||||||
|
extern spam [
|
||||||
|
animal: string@animals
|
||||||
|
--foo (-f): string@animals
|
||||||
|
-b: string@animals
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variables_dollar_sign_with_varialblecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "$ ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
assert_eq!(7, suggestions.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst --", 6);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||||
|
// dbg!(&expected, &suggestions);
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-c ", 4);
|
||||||
|
let expected: Vec<String> = vec!["my-command".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||||
|
mut completer_strings: NuCompleter,
|
||||||
|
) {
|
||||||
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dotnu_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test source completion
|
||||||
|
let completion_str = "source-env ".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||||
|
|
||||||
|
// Test use completion
|
||||||
|
let completion_str = "use ".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn external_completer_trailing_space() {
|
||||||
|
// https://github.com/nushell/nushell/issues/6378
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh alias ".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(3, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
|
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_completer_no_trailing_space() {
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh alias".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_completer_pass_flags() {
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh api --".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(3, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||||
|
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for the current folder
|
||||||
|
let target_dir = format!("cp {}", dir_str);
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
file(dir.join("nushell")),
|
||||||
|
folder(dir.join("test_a")),
|
||||||
|
folder(dir.join("test_b")),
|
||||||
|
folder(dir.join("another")),
|
||||||
|
file(dir.join("custom_completion.nu")),
|
||||||
|
file(dir.join(".hidden_file")),
|
||||||
|
folder(dir.join(".hidden_folder")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completions for a file
|
||||||
|
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_ls_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "ls ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn command_open_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "open ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_rm_with_globcompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "rm ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_cp_with_globcompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "cp ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_save_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "save ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_touch_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "touch ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_watch_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "watch ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_completion_quoted() {
|
||||||
|
let (_, _, engine, stack) = new_quote_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "open ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"`te st.txt`".to_string(),
|
||||||
|
"`te#st.txt`".to_string(),
|
||||||
|
"`te'st.txt`".to_string(),
|
||||||
|
"`te(st).txt`".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flag_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
// Test completions for the 'ls' flags
|
||||||
|
let suggestions = completer.complete("ls -", 4);
|
||||||
|
|
||||||
|
assert_eq!(16, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec![
|
||||||
|
"--all".into(),
|
||||||
|
"--directory".into(),
|
||||||
|
"--du".into(),
|
||||||
|
"--full-paths".into(),
|
||||||
|
"--help".into(),
|
||||||
|
"--long".into(),
|
||||||
|
"--mime-type".into(),
|
||||||
|
"--short-names".into(),
|
||||||
|
"-D".into(),
|
||||||
|
"-a".into(),
|
||||||
|
"-d".into(),
|
||||||
|
"-f".into(),
|
||||||
|
"-h".into(),
|
||||||
|
"-l".into(),
|
||||||
|
"-m".into(),
|
||||||
|
"-s".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn folder_with_directorycompletions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for the current folder
|
||||||
|
let target_dir = format!("cd {}", dir_str);
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
folder(dir.join("test_a")),
|
||||||
|
folder(dir.join("test_b")),
|
||||||
|
folder(dir.join("another")),
|
||||||
|
folder(dir.join(".hidden_folder")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variables_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for $nu
|
||||||
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
|
assert_eq!(9, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec![
|
||||||
|
"config-path".into(),
|
||||||
|
"env-path".into(),
|
||||||
|
"history-path".into(),
|
||||||
|
"home-path".into(),
|
||||||
|
"loginshell-path".into(),
|
||||||
|
"os-info".into(),
|
||||||
|
"pid".into(),
|
||||||
|
"scope".into(),
|
||||||
|
"temp-path".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $nu.h (filter)
|
||||||
|
let suggestions = completer.complete("$nu.h", 5);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for custom var
|
||||||
|
let suggestions = completer.complete("$actor.", 7);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for custom var (filtering)
|
||||||
|
let suggestions = completer.complete("$actor.n", 8);
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["name".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $env
|
||||||
|
let suggestions = completer.complete("$env.", 5);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $env
|
||||||
|
let suggestions = completer.complete("$env.T", 6);
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["TEST".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_command_and_flags() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls -l"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("ll t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_basic_command() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls "#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("ll t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_another_alias() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls -la"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||||
|
// Create the second alias
|
||||||
|
let alias = r#"alias lf = ll -f"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("lf t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||||
|
let (_, delta) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
|
||||||
|
assert!(err.is_none());
|
||||||
|
|
||||||
|
(block, working_set.render())
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(engine_state.merge_delta(delta).is_ok());
|
||||||
|
|
||||||
|
// Merge environment into the permanent state
|
||||||
|
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||||
|
|
||||||
|
let latest_block_id = engine_state.num_blocks() - 1;
|
||||||
|
|
||||||
|
// Change config adding the external completer
|
||||||
|
let mut config = engine_state.get_config().clone();
|
||||||
|
config.external_completer = Some(latest_block_id);
|
||||||
|
engine_state.set_config(&config);
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||||
|
|
||||||
|
completer.complete(input, input.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_command_completion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "thiscommanddoesnotexist ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h | ls", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filecompletions_triggers_after_cursor() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("cp test_c", 3);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam ", 5);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -", 6);
|
||||||
|
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variables_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, _, mut engine, mut stack) = new_engine();
|
|
||||||
|
|
||||||
// Add record value as example
|
|
||||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
|
||||||
def my-command [animal: string@animals] { print $animal }"#;
|
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for $nu
|
|
||||||
let suggestions = completer.complete("my-command ".into(), 11);
|
|
||||||
|
|
||||||
assert_eq!(3, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::new_engine;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dotnu_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test source completion
|
|
||||||
let completion_str = "source ".to_string();
|
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
|
||||||
|
|
||||||
// Test use completion
|
|
||||||
let completion_str = "use ".to_string();
|
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{file, folder, match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn file_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for the current folder
|
|
||||||
let target_dir = format!("cp {}", dir_str);
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![
|
|
||||||
file(dir.join("nushell")),
|
|
||||||
folder(dir.join("test_a")),
|
|
||||||
folder(dir.join("test_b")),
|
|
||||||
folder(dir.join("another")),
|
|
||||||
file(dir.join("custom_completion.nu")),
|
|
||||||
file(dir.join(".hidden_file")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
|
|
||||||
// Test completions for the completions/another folder
|
|
||||||
let target_dir = format!("cd {}", folder(dir.join("another")));
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn flag_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
// Test completions for the 'ls' flags
|
|
||||||
let suggestions = completer.complete("ls -".into(), 4);
|
|
||||||
|
|
||||||
assert_eq!(12, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"--all".into(),
|
|
||||||
"--du".into(),
|
|
||||||
"--full-paths".into(),
|
|
||||||
"--help".into(),
|
|
||||||
"--long".into(),
|
|
||||||
"--short-names".into(),
|
|
||||||
"-a".into(),
|
|
||||||
"-d".into(),
|
|
||||||
"-f".into(),
|
|
||||||
"-h".into(),
|
|
||||||
"-l".into(),
|
|
||||||
"-s".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{folder, match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn folder_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for the current folder
|
|
||||||
let target_dir = format!("cd {}", dir_str);
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![
|
|
||||||
folder(dir.join("test_a")),
|
|
||||||
folder(dir.join("test_b")),
|
|
||||||
folder(dir.join("another")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ use nu_command::create_default_context;
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, ShellError, Span, Value,
|
PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs;
|
use nu_test_support::fs;
|
||||||
@ -23,28 +23,68 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
dir_str.push(SEP);
|
dir_str.push(SEP);
|
||||||
|
|
||||||
// Create a new engine with default context
|
// Create a new engine with default context
|
||||||
let mut engine_state = create_default_context(&dir);
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
// New stack
|
// New stack
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
// New delta state
|
|
||||||
let delta = StateDelta::new(&engine_state);
|
|
||||||
|
|
||||||
// Add pwd as env var
|
// Add pwd as env var
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: dir_str.clone(),
|
val: dir_str.clone(),
|
||||||
span: nu_protocol::Span {
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
start: 0,
|
},
|
||||||
end: dir_str.len(),
|
);
|
||||||
},
|
stack.add_env_var(
|
||||||
|
"TEST".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: "NUSHELL".to_string(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
|
(dir, dir_str, engine_state, stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
|
// Target folder inside assets
|
||||||
|
let dir = fs::fixtures().join("quoted_completions");
|
||||||
|
let mut dir_str = dir
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap_or_default();
|
||||||
|
dir_str.push(SEP);
|
||||||
|
|
||||||
|
// Create a new engine with default context
|
||||||
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
|
// New stack
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// Add pwd as env var
|
||||||
|
stack.add_env_var(
|
||||||
|
"PWD".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: dir_str.clone(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stack.add_env_var(
|
||||||
|
"TEST".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: "NUSHELL".to_string(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge environment into the permanent state
|
||||||
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -52,6 +92,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
|
|
||||||
// match a list of suggestions with the expected values
|
// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
|
let expected_len = expected.len();
|
||||||
|
let suggestions_len = suggestions.len();
|
||||||
|
if expected_len != suggestions_len {
|
||||||
|
panic!(
|
||||||
|
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||||
|
Suggestions: {suggestions:#?} \n\
|
||||||
|
Expected: {expected:#?}\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
expected.iter().zip(suggestions).for_each(|it| {
|
expected.iter().zip(suggestions).for_each(|it| {
|
||||||
assert_eq!(it.0, &it.1.value);
|
assert_eq!(it.0, &it.1.value);
|
||||||
});
|
});
|
||||||
@ -79,7 +128,7 @@ pub fn merge_input(
|
|||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
||||||
|
|
||||||
@ -87,13 +136,16 @@ pub fn merge_input(
|
|||||||
|
|
||||||
(block, working_set.render())
|
(block, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
assert!(eval_block(
|
assert!(eval_block(
|
||||||
&engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&block,
|
&block,
|
||||||
PipelineData::Value(
|
PipelineData::Value(
|
||||||
Value::Nothing {
|
Value::Nothing {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
@ -102,6 +154,6 @@ pub fn merge_input(
|
|||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
engine_state.merge_env(stack, &dir)
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variables_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, _, mut engine, mut stack) = new_engine();
|
|
||||||
|
|
||||||
// Add record value as example
|
|
||||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for $nu
|
|
||||||
let suggestions = completer.complete("$nu.".into(), 4);
|
|
||||||
|
|
||||||
assert_eq!(8, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"config-path".into(),
|
|
||||||
"env-path".into(),
|
|
||||||
"history-path".into(),
|
|
||||||
"home-path".into(),
|
|
||||||
"os-info".into(),
|
|
||||||
"pid".into(),
|
|
||||||
"scope".into(),
|
|
||||||
"temp-path".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for custom var
|
|
||||||
let suggestions = completer.complete("$actor.".into(), 7);
|
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for $env
|
|
||||||
let suggestions = completer.complete("$env.".into(), 5);
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["PWD".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,14 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Color configuration code used by Nushell"
|
description = "Color configuration code used by Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.63.0"
|
version = "0.74.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.0" }
|
|
||||||
nu-ansi-term = "0.45.1"
|
|
||||||
nu-json = { path = "../nu-json", version = "0.63.0" }
|
|
||||||
nu-table = { path = "../nu-table", version = "0.63.0" }
|
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
|
# used only for text_style Alignments
|
||||||
|
tabled = { version = "0.10.0", features = ["color"], default-features = false }
|
||||||
|
|
||||||
|
nu-protocol = { path = "../nu-protocol", version = "0.74.0" }
|
||||||
|
nu-ansi-term = "0.46.0"
|
||||||
|
nu-utils = { path = "../nu-utils", version = "0.74.0" }
|
||||||
|
nu-engine = { path = "../nu-engine", version = "0.74.0" }
|
||||||
|
nu-json = { path="../nu-json", version = "0.74.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path="../nu-test-support", version = "0.74.0" }
|
||||||
|
@ -1,418 +1,89 @@
|
|||||||
use crate::nu_style::{color_from_hex, color_string_to_nustyle};
|
use crate::{
|
||||||
use nu_ansi_term::{Color, Style};
|
nu_style::{color_from_hex, lookup_style},
|
||||||
use nu_protocol::Config;
|
parse_nustyle, NuStyle,
|
||||||
use nu_table::{Alignment, TextStyle};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_protocol::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn lookup_ansi_color_style(s: &str) -> Style {
|
pub fn lookup_ansi_color_style(s: &str) -> Style {
|
||||||
if s.starts_with('#') {
|
if s.starts_with('#') {
|
||||||
match color_from_hex(s) {
|
color_from_hex(s)
|
||||||
Ok(c) => match c {
|
.ok()
|
||||||
Some(c) => c.normal(),
|
.and_then(|c| c.map(|c| c.normal()))
|
||||||
None => Style::default(),
|
.unwrap_or_default()
|
||||||
},
|
|
||||||
Err(_) => Style::default(),
|
|
||||||
}
|
|
||||||
} else if s.starts_with('{') {
|
} else if s.starts_with('{') {
|
||||||
color_string_to_nustyle(s.to_string())
|
color_string_to_nustyle(s.to_string())
|
||||||
} else {
|
} else {
|
||||||
match s {
|
lookup_style(s)
|
||||||
"g" | "green" => Color::Green.normal(),
|
|
||||||
"gb" | "green_bold" => Color::Green.bold(),
|
|
||||||
"gu" | "green_underline" => Color::Green.underline(),
|
|
||||||
"gi" | "green_italic" => Color::Green.italic(),
|
|
||||||
"gd" | "green_dimmed" => Color::Green.dimmed(),
|
|
||||||
"gr" | "green_reverse" => Color::Green.reverse(),
|
|
||||||
"gbl" | "green_blink" => Color::Green.blink(),
|
|
||||||
"gst" | "green_strike" => Color::Green.strikethrough(),
|
|
||||||
|
|
||||||
"lg" | "light_green" => Color::LightGreen.normal(),
|
|
||||||
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
|
|
||||||
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
|
|
||||||
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
|
|
||||||
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
|
|
||||||
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
|
|
||||||
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
|
|
||||||
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
|
|
||||||
|
|
||||||
"r" | "red" => Color::Red.normal(),
|
|
||||||
"rb" | "red_bold" => Color::Red.bold(),
|
|
||||||
"ru" | "red_underline" => Color::Red.underline(),
|
|
||||||
"ri" | "red_italic" => Color::Red.italic(),
|
|
||||||
"rd" | "red_dimmed" => Color::Red.dimmed(),
|
|
||||||
"rr" | "red_reverse" => Color::Red.reverse(),
|
|
||||||
"rbl" | "red_blink" => Color::Red.blink(),
|
|
||||||
"rst" | "red_strike" => Color::Red.strikethrough(),
|
|
||||||
|
|
||||||
"lr" | "light_red" => Color::LightRed.normal(),
|
|
||||||
"lrb" | "light_red_bold" => Color::LightRed.bold(),
|
|
||||||
"lru" | "light_red_underline" => Color::LightRed.underline(),
|
|
||||||
"lri" | "light_red_italic" => Color::LightRed.italic(),
|
|
||||||
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
|
|
||||||
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
|
|
||||||
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
|
|
||||||
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
|
|
||||||
|
|
||||||
"u" | "blue" => Color::Blue.normal(),
|
|
||||||
"ub" | "blue_bold" => Color::Blue.bold(),
|
|
||||||
"uu" | "blue_underline" => Color::Blue.underline(),
|
|
||||||
"ui" | "blue_italic" => Color::Blue.italic(),
|
|
||||||
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
|
|
||||||
"ur" | "blue_reverse" => Color::Blue.reverse(),
|
|
||||||
"ubl" | "blue_blink" => Color::Blue.blink(),
|
|
||||||
"ust" | "blue_strike" => Color::Blue.strikethrough(),
|
|
||||||
|
|
||||||
"lu" | "light_blue" => Color::LightBlue.normal(),
|
|
||||||
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
|
|
||||||
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
|
|
||||||
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
|
|
||||||
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
|
|
||||||
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
|
|
||||||
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
|
|
||||||
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
|
|
||||||
|
|
||||||
"b" | "black" => Color::Black.normal(),
|
|
||||||
"bb" | "black_bold" => Color::Black.bold(),
|
|
||||||
"bu" | "black_underline" => Color::Black.underline(),
|
|
||||||
"bi" | "black_italic" => Color::Black.italic(),
|
|
||||||
"bd" | "black_dimmed" => Color::Black.dimmed(),
|
|
||||||
"br" | "black_reverse" => Color::Black.reverse(),
|
|
||||||
"bbl" | "black_blink" => Color::Black.blink(),
|
|
||||||
"bst" | "black_strike" => Color::Black.strikethrough(),
|
|
||||||
|
|
||||||
"ligr" | "light_gray" => Color::LightGray.normal(),
|
|
||||||
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
|
|
||||||
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
|
|
||||||
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
|
|
||||||
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
|
|
||||||
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
|
|
||||||
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
|
|
||||||
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
|
|
||||||
|
|
||||||
"y" | "yellow" => Color::Yellow.normal(),
|
|
||||||
"yb" | "yellow_bold" => Color::Yellow.bold(),
|
|
||||||
"yu" | "yellow_underline" => Color::Yellow.underline(),
|
|
||||||
"yi" | "yellow_italic" => Color::Yellow.italic(),
|
|
||||||
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
|
|
||||||
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
|
|
||||||
"ybl" | "yellow_blink" => Color::Yellow.blink(),
|
|
||||||
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
|
|
||||||
|
|
||||||
"ly" | "light_yellow" => Color::LightYellow.normal(),
|
|
||||||
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
|
|
||||||
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
|
|
||||||
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
|
|
||||||
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
|
|
||||||
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
|
|
||||||
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
|
|
||||||
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
|
|
||||||
|
|
||||||
"p" | "purple" => Color::Purple.normal(),
|
|
||||||
"pb" | "purple_bold" => Color::Purple.bold(),
|
|
||||||
"pu" | "purple_underline" => Color::Purple.underline(),
|
|
||||||
"pi" | "purple_italic" => Color::Purple.italic(),
|
|
||||||
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
|
|
||||||
"pr" | "purple_reverse" => Color::Purple.reverse(),
|
|
||||||
"pbl" | "purple_blink" => Color::Purple.blink(),
|
|
||||||
"pst" | "purple_strike" => Color::Purple.strikethrough(),
|
|
||||||
|
|
||||||
"lp" | "light_purple" => Color::LightPurple.normal(),
|
|
||||||
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
|
|
||||||
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
|
|
||||||
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
|
|
||||||
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
|
|
||||||
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
|
|
||||||
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
|
|
||||||
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
|
|
||||||
|
|
||||||
"c" | "cyan" => Color::Cyan.normal(),
|
|
||||||
"cb" | "cyan_bold" => Color::Cyan.bold(),
|
|
||||||
"cu" | "cyan_underline" => Color::Cyan.underline(),
|
|
||||||
"ci" | "cyan_italic" => Color::Cyan.italic(),
|
|
||||||
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
|
|
||||||
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
|
|
||||||
"cbl" | "cyan_blink" => Color::Cyan.blink(),
|
|
||||||
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
|
|
||||||
|
|
||||||
"lc" | "light_cyan" => Color::LightCyan.normal(),
|
|
||||||
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
|
|
||||||
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
|
|
||||||
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
|
|
||||||
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
|
|
||||||
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
|
|
||||||
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
|
|
||||||
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
|
|
||||||
|
|
||||||
"w" | "white" => Color::White.normal(),
|
|
||||||
"wb" | "white_bold" => Color::White.bold(),
|
|
||||||
"wu" | "white_underline" => Color::White.underline(),
|
|
||||||
"wi" | "white_italic" => Color::White.italic(),
|
|
||||||
"wd" | "white_dimmed" => Color::White.dimmed(),
|
|
||||||
"wr" | "white_reverse" => Color::White.reverse(),
|
|
||||||
"wbl" | "white_blink" => Color::White.blink(),
|
|
||||||
"wst" | "white_strike" => Color::White.strikethrough(),
|
|
||||||
|
|
||||||
"dgr" | "dark_gray" => Color::DarkGray.normal(),
|
|
||||||
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
|
|
||||||
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
|
|
||||||
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
|
|
||||||
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
|
|
||||||
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
|
|
||||||
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
|
|
||||||
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
|
|
||||||
|
|
||||||
"def" | "default" => Color::Default.normal(),
|
|
||||||
"defb" | "default_bold" => Color::Default.bold(),
|
|
||||||
"defu" | "default_underline" => Color::Default.underline(),
|
|
||||||
"defi" | "default_italic" => Color::Default.italic(),
|
|
||||||
"defd" | "default_dimmed" => Color::Default.dimmed(),
|
|
||||||
"defr" | "default_reverse" => Color::Default.reverse(),
|
|
||||||
|
|
||||||
_ => Color::White.normal(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hashmap(key: &str, val: &str, hm: &mut HashMap<String, Style>) {
|
pub fn get_color_map(colors: &HashMap<String, Value>) -> HashMap<String, Style> {
|
||||||
// eprintln!("key: {}, val: {}", &key, &val);
|
|
||||||
let color = lookup_ansi_color_style(val);
|
|
||||||
if let Some(v) = hm.get_mut(key) {
|
|
||||||
*v = color;
|
|
||||||
} else {
|
|
||||||
hm.insert(key.to_string(), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_color_config(config: &Config) -> HashMap<String, Style> {
|
|
||||||
let config = config;
|
|
||||||
|
|
||||||
// create the hashmap
|
|
||||||
let mut hm: HashMap<String, Style> = HashMap::new();
|
let mut hm: HashMap<String, Style> = HashMap::new();
|
||||||
// set some defaults
|
|
||||||
// hm.insert("primitive_line".to_string(), Color::White.normal());
|
|
||||||
// hm.insert("primitive_pattern".to_string(), Color::White.normal());
|
|
||||||
// hm.insert("primitive_path".to_string(), Color::White.normal());
|
|
||||||
hm.insert("separator".to_string(), Color::White.normal());
|
|
||||||
hm.insert(
|
|
||||||
"leading_trailing_space_bg".to_string(),
|
|
||||||
Style::default().on(Color::Rgb(128, 128, 128)),
|
|
||||||
);
|
|
||||||
hm.insert("header".to_string(), Color::Green.bold());
|
|
||||||
hm.insert("empty".to_string(), Color::Blue.normal());
|
|
||||||
hm.insert("bool".to_string(), Color::White.normal());
|
|
||||||
hm.insert("int".to_string(), Color::White.normal());
|
|
||||||
hm.insert("filesize".to_string(), Color::White.normal());
|
|
||||||
hm.insert("duration".to_string(), Color::White.normal());
|
|
||||||
hm.insert("date".to_string(), Color::White.normal());
|
|
||||||
hm.insert("range".to_string(), Color::White.normal());
|
|
||||||
hm.insert("float".to_string(), Color::White.normal());
|
|
||||||
hm.insert("string".to_string(), Color::White.normal());
|
|
||||||
hm.insert("nothing".to_string(), Color::White.normal());
|
|
||||||
hm.insert("binary".to_string(), Color::White.normal());
|
|
||||||
hm.insert("cellpath".to_string(), Color::White.normal());
|
|
||||||
hm.insert("row_index".to_string(), Color::Green.bold());
|
|
||||||
hm.insert("record".to_string(), Color::White.normal());
|
|
||||||
hm.insert("list".to_string(), Color::White.normal());
|
|
||||||
hm.insert("block".to_string(), Color::White.normal());
|
|
||||||
hm.insert("hints".to_string(), Color::DarkGray.normal());
|
|
||||||
|
|
||||||
for (key, value) in &config.color_config {
|
for (key, value) in colors {
|
||||||
let value = value
|
parse_map_entry(&mut hm, key, value);
|
||||||
.as_string()
|
|
||||||
.expect("the only values for config color must be strings");
|
|
||||||
update_hashmap(key, &value, &mut hm);
|
|
||||||
|
|
||||||
// eprintln!(
|
|
||||||
// "config: {}:{}\t\t\thashmap: {}:{:?}",
|
|
||||||
// &key, &value, &key, &hm[key]
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hm
|
hm
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will assign a text style to a primitive, or really any string that's
|
fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) {
|
||||||
// in the hashmap. The hashmap actually contains the style to be applied.
|
let value = match value {
|
||||||
pub fn style_primitive(primitive: &str, color_hm: &HashMap<String, Style>) -> TextStyle {
|
Value::String { val, .. } => Some(lookup_ansi_color_style(val)),
|
||||||
match primitive {
|
Value::Record { cols, vals, .. } => get_style_from_value(cols, vals).map(parse_nustyle),
|
||||||
"bool" => {
|
_ => None,
|
||||||
let style = color_hm.get(primitive);
|
};
|
||||||
match style {
|
if let Some(value) = value {
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
hm.entry(key.to_owned()).or_insert(value);
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"int" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"filesize" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"duration" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"date" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"range" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"float" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"string" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"nothing" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not sure what to do with error
|
|
||||||
// "error" => {}
|
|
||||||
"binary" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"cellpath" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"row_index" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::new()
|
|
||||||
.alignment(Alignment::Right)
|
|
||||||
.fg(Color::Green)
|
|
||||||
.bold(Some(true)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"record" | "list" | "block" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// types in nushell but not in engine-q
|
|
||||||
// "Line" => {
|
|
||||||
// let style = color_hm.get("Primitive::Line");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "GlobPattern" => {
|
|
||||||
// let style = color_hm.get("Primitive::GlobPattern");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "FilePath" => {
|
|
||||||
// let style = color_hm.get("Primitive::FilePath");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "BeginningOfStream" => {
|
|
||||||
// let style = color_hm.get("Primitive::BeginningOfStream");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "EndOfStream" => {
|
|
||||||
// let style = color_hm.get("Primitive::EndOfStream");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
_ => TextStyle::basic_left(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn get_style_from_value(cols: &[String], vals: &[Value]) -> Option<NuStyle> {
|
||||||
fn test_hm() {
|
let mut was_set = false;
|
||||||
use nu_ansi_term::{Color, Style};
|
let mut style = NuStyle::from(Style::default());
|
||||||
|
for (col, val) in cols.iter().zip(vals) {
|
||||||
|
match col.as_str() {
|
||||||
|
"bg" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.bg = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"fg" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.fg = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"attr" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.attr = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut hm: HashMap<String, Style> = HashMap::new();
|
if was_set {
|
||||||
hm.insert("primitive_int".to_string(), Color::White.normal());
|
Some(style)
|
||||||
hm.insert("primitive_decimal".to_string(), Color::White.normal());
|
} else {
|
||||||
hm.insert("primitive_filesize".to_string(), Color::White.normal());
|
None
|
||||||
hm.insert("primitive_string".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_line".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_columnpath".to_string(), Color::White.normal());
|
|
||||||
hm.insert("primitive_pattern".to_string(), Color::White.normal());
|
fn color_string_to_nustyle(color_string: String) -> Style {
|
||||||
hm.insert("primitive_boolean".to_string(), Color::White.normal());
|
// eprintln!("color_string: {}", &color_string);
|
||||||
hm.insert("primitive_date".to_string(), Color::White.normal());
|
if color_string.is_empty() {
|
||||||
hm.insert("primitive_duration".to_string(), Color::White.normal());
|
return Style::default();
|
||||||
hm.insert("primitive_range".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_path".to_string(), Color::White.normal());
|
|
||||||
hm.insert("primitive_binary".to_string(), Color::White.normal());
|
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
|
||||||
hm.insert("separator".to_string(), Color::White.normal());
|
Ok(s) => s,
|
||||||
hm.insert("header_align".to_string(), Color::Green.bold());
|
Err(_) => return Style::default(),
|
||||||
hm.insert("header".to_string(), Color::Green.bold());
|
};
|
||||||
hm.insert("header_style".to_string(), Style::default());
|
|
||||||
hm.insert("row_index".to_string(), Color::Green.bold());
|
parse_nustyle(nu_style)
|
||||||
hm.insert(
|
|
||||||
"leading_trailing_space_bg".to_string(),
|
|
||||||
Style::default().on(Color::Rgb(128, 128, 128)),
|
|
||||||
);
|
|
||||||
|
|
||||||
update_hashmap("primitive_int", "green", &mut hm);
|
|
||||||
|
|
||||||
assert_eq!(hm["primitive_int"], Color::Green.normal());
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
mod color_config;
|
mod color_config;
|
||||||
|
mod matching_brackets_style;
|
||||||
mod nu_style;
|
mod nu_style;
|
||||||
mod shape_color;
|
mod shape_color;
|
||||||
|
mod style_computer;
|
||||||
|
mod text_style;
|
||||||
|
|
||||||
pub use color_config::*;
|
pub use color_config::*;
|
||||||
|
pub use matching_brackets_style::*;
|
||||||
pub use nu_style::*;
|
pub use nu_style::*;
|
||||||
pub use shape_color::*;
|
pub use shape_color::*;
|
||||||
|
pub use style_computer::*;
|
||||||
|
pub use text_style::*;
|
||||||
|
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::color_config::lookup_ansi_color_style;
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_protocol::Config;
|
||||||
|
|
||||||
|
pub fn get_matching_brackets_style(default_style: Style, conf: &Config) -> Style {
|
||||||
|
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
|
||||||
|
|
||||||
|
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
|
||||||
|
Some(int_color) => match int_color.as_string() {
|
||||||
|
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
|
||||||
|
Err(_) => default_style,
|
||||||
|
},
|
||||||
|
None => default_style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_styles(base: Style, extra: Style) -> Style {
|
||||||
|
Style {
|
||||||
|
foreground: extra.foreground.or(base.foreground),
|
||||||
|
background: extra.background.or(base.background),
|
||||||
|
is_bold: extra.is_bold || base.is_bold,
|
||||||
|
is_dimmed: extra.is_dimmed || base.is_dimmed,
|
||||||
|
is_italic: extra.is_italic || base.is_italic,
|
||||||
|
is_underline: extra.is_underline || base.is_underline,
|
||||||
|
is_blink: extra.is_blink || base.is_blink,
|
||||||
|
is_reverse: extra.is_reverse || base.is_reverse,
|
||||||
|
is_hidden: extra.is_hidden || base.is_hidden,
|
||||||
|
is_strikethrough: extra.is_strikethrough || base.is_strikethrough,
|
||||||
|
}
|
||||||
|
}
|
@ -1,88 +1,113 @@
|
|||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use serde::Deserialize;
|
use nu_protocol::Value;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Debug)]
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug)]
|
||||||
pub struct NuStyle {
|
pub struct NuStyle {
|
||||||
pub fg: Option<String>,
|
pub fg: Option<String>,
|
||||||
pub bg: Option<String>,
|
pub bg: Option<String>,
|
||||||
pub attr: Option<String>,
|
pub attr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
impl From<Style> for NuStyle {
|
||||||
// get the nu_ansi_term::Color foreground color
|
fn from(s: Style) -> Self {
|
||||||
let fg_color = match nu_style.fg {
|
Self {
|
||||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
bg: s.background.and_then(color_to_string),
|
||||||
_ => None,
|
fg: s.foreground.and_then(color_to_string),
|
||||||
};
|
attr: style_get_attr(s),
|
||||||
// get the nu_ansi_term::Color background color
|
|
||||||
let bg_color = match nu_style.bg {
|
|
||||||
Some(bg) => color_from_hex(&bg).expect("error with background color"),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
// get the attributes
|
|
||||||
let color_attr = match nu_style.attr {
|
|
||||||
Some(attr) => attr,
|
|
||||||
_ => "".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// setup the attributes available in nu_ansi_term::Style
|
|
||||||
let mut bold = false;
|
|
||||||
let mut dimmed = false;
|
|
||||||
let mut italic = false;
|
|
||||||
let mut underline = false;
|
|
||||||
let mut blink = false;
|
|
||||||
let mut reverse = false;
|
|
||||||
let mut hidden = false;
|
|
||||||
let mut strikethrough = false;
|
|
||||||
|
|
||||||
// since we can combine styles like bold-italic, iterate through the chars
|
|
||||||
// and set the bools for later use in the nu_ansi_term::Style application
|
|
||||||
for ch in color_attr.to_lowercase().chars() {
|
|
||||||
match ch {
|
|
||||||
'l' => blink = true,
|
|
||||||
'b' => bold = true,
|
|
||||||
'd' => dimmed = true,
|
|
||||||
'h' => hidden = true,
|
|
||||||
'i' => italic = true,
|
|
||||||
'r' => reverse = true,
|
|
||||||
's' => strikethrough = true,
|
|
||||||
'u' => underline = true,
|
|
||||||
'n' => (),
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// here's where we build the nu_ansi_term::Style
|
|
||||||
Style {
|
|
||||||
foreground: fg_color,
|
|
||||||
background: bg_color,
|
|
||||||
is_blink: blink,
|
|
||||||
is_bold: bold,
|
|
||||||
is_dimmed: dimmed,
|
|
||||||
is_hidden: hidden,
|
|
||||||
is_italic: italic,
|
|
||||||
is_reverse: reverse,
|
|
||||||
is_strikethrough: strikethrough,
|
|
||||||
is_underline: underline,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_string_to_nustyle(color_string: String) -> Style {
|
fn style_get_attr(s: Style) -> Option<String> {
|
||||||
// eprintln!("color_string: {}", &color_string);
|
macro_rules! check {
|
||||||
if color_string.chars().count() < 1 {
|
($attrs:expr, $b:expr, $c:expr) => {
|
||||||
Style::default()
|
if $b {
|
||||||
} else {
|
$attrs.push($c);
|
||||||
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
|
}
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => NuStyle {
|
|
||||||
fg: None,
|
|
||||||
bg: None,
|
|
||||||
attr: None,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
parse_nustyle(nu_style)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut attrs = String::new();
|
||||||
|
|
||||||
|
check!(attrs, s.is_blink, 'l');
|
||||||
|
check!(attrs, s.is_bold, 'b');
|
||||||
|
check!(attrs, s.is_dimmed, 'd');
|
||||||
|
check!(attrs, s.is_reverse, 'r');
|
||||||
|
check!(attrs, s.is_strikethrough, 's');
|
||||||
|
check!(attrs, s.is_underline, 'u');
|
||||||
|
|
||||||
|
if attrs.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_to_string(color: Color) -> Option<String> {
|
||||||
|
match color {
|
||||||
|
Color::Black => Some(String::from("black")),
|
||||||
|
Color::DarkGray => Some(String::from("dark_gray")),
|
||||||
|
Color::Red => Some(String::from("red")),
|
||||||
|
Color::LightRed => Some(String::from("light_red")),
|
||||||
|
Color::Green => Some(String::from("green")),
|
||||||
|
Color::LightGreen => Some(String::from("light_green")),
|
||||||
|
Color::Yellow => Some(String::from("yellow")),
|
||||||
|
Color::LightYellow => Some(String::from("light_yellow")),
|
||||||
|
Color::Blue => Some(String::from("blue")),
|
||||||
|
Color::LightBlue => Some(String::from("light_blue")),
|
||||||
|
Color::Purple => Some(String::from("purple")),
|
||||||
|
Color::LightPurple => Some(String::from("light_purple")),
|
||||||
|
Color::Magenta => Some(String::from("magenta")),
|
||||||
|
Color::LightMagenta => Some(String::from("light_magenta")),
|
||||||
|
Color::Cyan => Some(String::from("cyan")),
|
||||||
|
Color::LightCyan => Some(String::from("light_cyan")),
|
||||||
|
Color::White => Some(String::from("white")),
|
||||||
|
Color::LightGray => Some(String::from("light_gray")),
|
||||||
|
Color::Default => Some(String::from("default")),
|
||||||
|
Color::Rgb(r, g, b) => Some(format!("#{:X}{:X}{:X}", r, g, b)),
|
||||||
|
Color::Fixed(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||||
|
let mut style = Style {
|
||||||
|
foreground: nu_style.fg.and_then(|fg| lookup_color_str(&fg)),
|
||||||
|
background: nu_style.bg.and_then(|bg| lookup_color_str(&bg)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(attrs) = nu_style.attr {
|
||||||
|
fill_modifiers(&attrs, &mut style)
|
||||||
|
}
|
||||||
|
|
||||||
|
style
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the color_config records, { fg, bg, attr }, into a Style.
|
||||||
|
pub fn color_record_to_nustyle(value: &Value) -> Style {
|
||||||
|
let mut fg = None;
|
||||||
|
let mut bg = None;
|
||||||
|
let mut attr = None;
|
||||||
|
let v = value.as_record();
|
||||||
|
if let Ok((cols, inner_vals)) = v {
|
||||||
|
for (k, v) in cols.iter().zip(inner_vals) {
|
||||||
|
// Because config already type-checked the color_config records, this doesn't bother giving errors
|
||||||
|
// if there are unrecognised keys or bad values.
|
||||||
|
if let Ok(v) = v.as_string() {
|
||||||
|
match k.as_str() {
|
||||||
|
"fg" => fg = Some(v),
|
||||||
|
|
||||||
|
"bg" => bg = Some(v),
|
||||||
|
|
||||||
|
"attr" => attr = Some(v),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_nustyle(NuStyle { fg, bg, attr })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_from_hex(
|
pub fn color_from_hex(
|
||||||
@ -101,3 +126,471 @@ pub fn color_from_hex(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lookup_style(s: &str) -> Style {
|
||||||
|
match s {
|
||||||
|
"g" | "green" => Color::Green.normal(),
|
||||||
|
"gb" | "green_bold" => Color::Green.bold(),
|
||||||
|
"gu" | "green_underline" => Color::Green.underline(),
|
||||||
|
"gi" | "green_italic" => Color::Green.italic(),
|
||||||
|
"gd" | "green_dimmed" => Color::Green.dimmed(),
|
||||||
|
"gr" | "green_reverse" => Color::Green.reverse(),
|
||||||
|
"gbl" | "green_blink" => Color::Green.blink(),
|
||||||
|
"gst" | "green_strike" => Color::Green.strikethrough(),
|
||||||
|
|
||||||
|
"lg" | "light_green" => Color::LightGreen.normal(),
|
||||||
|
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
|
||||||
|
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
|
||||||
|
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
|
||||||
|
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
|
||||||
|
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
|
||||||
|
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
|
||||||
|
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
|
||||||
|
|
||||||
|
"r" | "red" => Color::Red.normal(),
|
||||||
|
"rb" | "red_bold" => Color::Red.bold(),
|
||||||
|
"ru" | "red_underline" => Color::Red.underline(),
|
||||||
|
"ri" | "red_italic" => Color::Red.italic(),
|
||||||
|
"rd" | "red_dimmed" => Color::Red.dimmed(),
|
||||||
|
"rr" | "red_reverse" => Color::Red.reverse(),
|
||||||
|
"rbl" | "red_blink" => Color::Red.blink(),
|
||||||
|
"rst" | "red_strike" => Color::Red.strikethrough(),
|
||||||
|
|
||||||
|
"lr" | "light_red" => Color::LightRed.normal(),
|
||||||
|
"lrb" | "light_red_bold" => Color::LightRed.bold(),
|
||||||
|
"lru" | "light_red_underline" => Color::LightRed.underline(),
|
||||||
|
"lri" | "light_red_italic" => Color::LightRed.italic(),
|
||||||
|
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
|
||||||
|
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
|
||||||
|
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
|
||||||
|
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
|
||||||
|
|
||||||
|
"u" | "blue" => Color::Blue.normal(),
|
||||||
|
"ub" | "blue_bold" => Color::Blue.bold(),
|
||||||
|
"uu" | "blue_underline" => Color::Blue.underline(),
|
||||||
|
"ui" | "blue_italic" => Color::Blue.italic(),
|
||||||
|
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
|
||||||
|
"ur" | "blue_reverse" => Color::Blue.reverse(),
|
||||||
|
"ubl" | "blue_blink" => Color::Blue.blink(),
|
||||||
|
"ust" | "blue_strike" => Color::Blue.strikethrough(),
|
||||||
|
|
||||||
|
"lu" | "light_blue" => Color::LightBlue.normal(),
|
||||||
|
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
|
||||||
|
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
|
||||||
|
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
|
||||||
|
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
|
||||||
|
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
|
||||||
|
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
|
||||||
|
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
|
||||||
|
|
||||||
|
"b" | "black" => Color::Black.normal(),
|
||||||
|
"bb" | "black_bold" => Color::Black.bold(),
|
||||||
|
"bu" | "black_underline" => Color::Black.underline(),
|
||||||
|
"bi" | "black_italic" => Color::Black.italic(),
|
||||||
|
"bd" | "black_dimmed" => Color::Black.dimmed(),
|
||||||
|
"br" | "black_reverse" => Color::Black.reverse(),
|
||||||
|
"bbl" | "black_blink" => Color::Black.blink(),
|
||||||
|
"bst" | "black_strike" => Color::Black.strikethrough(),
|
||||||
|
|
||||||
|
"ligr" | "light_gray" => Color::LightGray.normal(),
|
||||||
|
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
|
||||||
|
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
|
||||||
|
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
|
||||||
|
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
|
||||||
|
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
|
||||||
|
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
|
||||||
|
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
|
||||||
|
|
||||||
|
"y" | "yellow" => Color::Yellow.normal(),
|
||||||
|
"yb" | "yellow_bold" => Color::Yellow.bold(),
|
||||||
|
"yu" | "yellow_underline" => Color::Yellow.underline(),
|
||||||
|
"yi" | "yellow_italic" => Color::Yellow.italic(),
|
||||||
|
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
|
||||||
|
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
|
||||||
|
"ybl" | "yellow_blink" => Color::Yellow.blink(),
|
||||||
|
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
|
||||||
|
|
||||||
|
"ly" | "light_yellow" => Color::LightYellow.normal(),
|
||||||
|
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
|
||||||
|
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
|
||||||
|
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
|
||||||
|
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
|
||||||
|
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
|
||||||
|
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
|
||||||
|
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
|
||||||
|
|
||||||
|
"p" | "purple" => Color::Purple.normal(),
|
||||||
|
"pb" | "purple_bold" => Color::Purple.bold(),
|
||||||
|
"pu" | "purple_underline" => Color::Purple.underline(),
|
||||||
|
"pi" | "purple_italic" => Color::Purple.italic(),
|
||||||
|
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
|
||||||
|
"pr" | "purple_reverse" => Color::Purple.reverse(),
|
||||||
|
"pbl" | "purple_blink" => Color::Purple.blink(),
|
||||||
|
"pst" | "purple_strike" => Color::Purple.strikethrough(),
|
||||||
|
|
||||||
|
"lp" | "light_purple" => Color::LightPurple.normal(),
|
||||||
|
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
|
||||||
|
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
|
||||||
|
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
|
||||||
|
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
|
||||||
|
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
|
||||||
|
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
|
||||||
|
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
|
||||||
|
|
||||||
|
"c" | "cyan" => Color::Cyan.normal(),
|
||||||
|
"cb" | "cyan_bold" => Color::Cyan.bold(),
|
||||||
|
"cu" | "cyan_underline" => Color::Cyan.underline(),
|
||||||
|
"ci" | "cyan_italic" => Color::Cyan.italic(),
|
||||||
|
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
|
||||||
|
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
|
||||||
|
"cbl" | "cyan_blink" => Color::Cyan.blink(),
|
||||||
|
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
|
||||||
|
|
||||||
|
"lc" | "light_cyan" => Color::LightCyan.normal(),
|
||||||
|
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
|
||||||
|
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
|
||||||
|
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
|
||||||
|
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
|
||||||
|
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
|
||||||
|
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
|
||||||
|
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
|
||||||
|
|
||||||
|
"w" | "white" => Color::White.normal(),
|
||||||
|
"wb" | "white_bold" => Color::White.bold(),
|
||||||
|
"wu" | "white_underline" => Color::White.underline(),
|
||||||
|
"wi" | "white_italic" => Color::White.italic(),
|
||||||
|
"wd" | "white_dimmed" => Color::White.dimmed(),
|
||||||
|
"wr" | "white_reverse" => Color::White.reverse(),
|
||||||
|
"wbl" | "white_blink" => Color::White.blink(),
|
||||||
|
"wst" | "white_strike" => Color::White.strikethrough(),
|
||||||
|
|
||||||
|
"dgr" | "dark_gray" => Color::DarkGray.normal(),
|
||||||
|
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
|
||||||
|
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
|
||||||
|
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
|
||||||
|
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
|
||||||
|
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
|
||||||
|
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
|
||||||
|
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
|
||||||
|
|
||||||
|
"def" | "default" => Color::Default.normal(),
|
||||||
|
"defb" | "default_bold" => Color::Default.bold(),
|
||||||
|
"defu" | "default_underline" => Color::Default.underline(),
|
||||||
|
"defi" | "default_italic" => Color::Default.italic(),
|
||||||
|
"defd" | "default_dimmed" => Color::Default.dimmed(),
|
||||||
|
"defr" | "default_reverse" => Color::Default.reverse(),
|
||||||
|
|
||||||
|
// Add xterm 256 colors adding an x prefix where the name conflicts
|
||||||
|
"xblack" | "xterm_black" => Color::Fixed(0).normal(),
|
||||||
|
"maroon" | "xterm_maroon" => Color::Fixed(1).normal(),
|
||||||
|
"xgreen" | "xterm_green" => Color::Fixed(2).normal(),
|
||||||
|
"olive" | "xterm_olive" => Color::Fixed(3).normal(),
|
||||||
|
"navy" | "xterm_navy" => Color::Fixed(4).normal(),
|
||||||
|
"xpurplea" | "xterm_purplea" => Color::Fixed(5).normal(),
|
||||||
|
"teal" | "xterm_teal" => Color::Fixed(6).normal(),
|
||||||
|
"silver" | "xterm_silver" => Color::Fixed(7).normal(),
|
||||||
|
"grey" | "xterm_grey" => Color::Fixed(8).normal(),
|
||||||
|
"xred" | "xterm_red" => Color::Fixed(9).normal(),
|
||||||
|
"lime" | "xterm_lime" => Color::Fixed(10).normal(),
|
||||||
|
"xyellow" | "xterm_yellow" => Color::Fixed(11).normal(),
|
||||||
|
"xblue" | "xterm_blue" => Color::Fixed(12).normal(),
|
||||||
|
"fuchsia" | "xterm_fuchsia" => Color::Fixed(13).normal(),
|
||||||
|
"aqua" | "xterm_aqua" => Color::Fixed(14).normal(),
|
||||||
|
"xwhite" | "xterm_white" => Color::Fixed(15).normal(),
|
||||||
|
"grey0" | "xterm_grey0" => Color::Fixed(16).normal(),
|
||||||
|
"navyblue" | "xterm_navyblue" => Color::Fixed(17).normal(),
|
||||||
|
"darkblue" | "xterm_darkblue" => Color::Fixed(18).normal(),
|
||||||
|
"blue3a" | "xterm_blue3a" => Color::Fixed(19).normal(),
|
||||||
|
"blue3b" | "xterm_blue3b" => Color::Fixed(20).normal(),
|
||||||
|
"blue1" | "xterm_blue1" => Color::Fixed(21).normal(),
|
||||||
|
"darkgreen" | "xterm_darkgreen" => Color::Fixed(22).normal(),
|
||||||
|
"deepskyblue4a" | "xterm_deepskyblue4a" => Color::Fixed(23).normal(),
|
||||||
|
"deepskyblue4b" | "xterm_deepskyblue4b" => Color::Fixed(24).normal(),
|
||||||
|
"deepskyblue4c" | "xterm_deepskyblue4c" => Color::Fixed(25).normal(),
|
||||||
|
"dodgerblue3" | "xterm_dodgerblue3" => Color::Fixed(26).normal(),
|
||||||
|
"dodgerblue2" | "xterm_dodgerblue2" => Color::Fixed(27).normal(),
|
||||||
|
"green4" | "xterm_green4" => Color::Fixed(28).normal(),
|
||||||
|
"springgreen4" | "xterm_springgreen4" => Color::Fixed(29).normal(),
|
||||||
|
"turquoise4" | "xterm_turquoise4" => Color::Fixed(30).normal(),
|
||||||
|
"deepskyblue3a" | "xterm_deepskyblue3a" => Color::Fixed(31).normal(),
|
||||||
|
"deepskyblue3b" | "xterm_deepskyblue3b" => Color::Fixed(32).normal(),
|
||||||
|
"dodgerblue1" | "xterm_dodgerblue1" => Color::Fixed(33).normal(),
|
||||||
|
"green3a" | "xterm_green3a" => Color::Fixed(34).normal(),
|
||||||
|
"springgreen3a" | "xterm_springgreen3a" => Color::Fixed(35).normal(),
|
||||||
|
"darkcyan" | "xterm_darkcyan" => Color::Fixed(36).normal(),
|
||||||
|
"lightseagreen" | "xterm_lightseagreen" => Color::Fixed(37).normal(),
|
||||||
|
"deepskyblue2" | "xterm_deepskyblue2" => Color::Fixed(38).normal(),
|
||||||
|
"deepskyblue1" | "xterm_deepskyblue1" => Color::Fixed(39).normal(),
|
||||||
|
"green3b" | "xterm_green3b" => Color::Fixed(40).normal(),
|
||||||
|
"springgreen3b" | "xterm_springgreen3b" => Color::Fixed(41).normal(),
|
||||||
|
"springgreen2a" | "xterm_springgreen2a" => Color::Fixed(42).normal(),
|
||||||
|
"cyan3" | "xterm_cyan3" => Color::Fixed(43).normal(),
|
||||||
|
"darkturquoise" | "xterm_darkturquoise" => Color::Fixed(44).normal(),
|
||||||
|
"turquoise2" | "xterm_turquoise2" => Color::Fixed(45).normal(),
|
||||||
|
"green1" | "xterm_green1" => Color::Fixed(46).normal(),
|
||||||
|
"springgreen2b" | "xterm_springgreen2b" => Color::Fixed(47).normal(),
|
||||||
|
"springgreen1" | "xterm_springgreen1" => Color::Fixed(48).normal(),
|
||||||
|
"mediumspringgreen" | "xterm_mediumspringgreen" => Color::Fixed(49).normal(),
|
||||||
|
"cyan2" | "xterm_cyan2" => Color::Fixed(50).normal(),
|
||||||
|
"cyan1" | "xterm_cyan1" => Color::Fixed(51).normal(),
|
||||||
|
"darkreda" | "xterm_darkreda" => Color::Fixed(52).normal(),
|
||||||
|
"deeppink4a" | "xterm_deeppink4a" => Color::Fixed(53).normal(),
|
||||||
|
"purple4a" | "xterm_purple4a" => Color::Fixed(54).normal(),
|
||||||
|
"purple4b" | "xterm_purple4b" => Color::Fixed(55).normal(),
|
||||||
|
"purple3" | "xterm_purple3" => Color::Fixed(56).normal(),
|
||||||
|
"blueviolet" | "xterm_blueviolet" => Color::Fixed(57).normal(),
|
||||||
|
"orange4a" | "xterm_orange4a" => Color::Fixed(58).normal(),
|
||||||
|
"grey37" | "xterm_grey37" => Color::Fixed(59).normal(),
|
||||||
|
"mediumpurple4" | "xterm_mediumpurple4" => Color::Fixed(60).normal(),
|
||||||
|
"slateblue3a" | "xterm_slateblue3a" => Color::Fixed(61).normal(),
|
||||||
|
"slateblue3b" | "xterm_slateblue3b" => Color::Fixed(62).normal(),
|
||||||
|
"royalblue1" | "xterm_royalblue1" => Color::Fixed(63).normal(),
|
||||||
|
"chartreuse4" | "xterm_chartreuse4" => Color::Fixed(64).normal(),
|
||||||
|
"darkseagreen4a" | "xterm_darkseagreen4a" => Color::Fixed(65).normal(),
|
||||||
|
"paleturquoise4" | "xterm_paleturquoise4" => Color::Fixed(66).normal(),
|
||||||
|
"steelblue" | "xterm_steelblue" => Color::Fixed(67).normal(),
|
||||||
|
"steelblue3" | "xterm_steelblue3" => Color::Fixed(68).normal(),
|
||||||
|
"cornflowerblue" | "xterm_cornflowerblue" => Color::Fixed(69).normal(),
|
||||||
|
"chartreuse3a" | "xterm_chartreuse3a" => Color::Fixed(70).normal(),
|
||||||
|
"darkseagreen4b" | "xterm_darkseagreen4b" => Color::Fixed(71).normal(),
|
||||||
|
"cadetbluea" | "xterm_cadetbluea" => Color::Fixed(72).normal(),
|
||||||
|
"cadetblueb" | "xterm_cadetblueb" => Color::Fixed(73).normal(),
|
||||||
|
"skyblue3" | "xterm_skyblue3" => Color::Fixed(74).normal(),
|
||||||
|
"steelblue1a" | "xterm_steelblue1a" => Color::Fixed(75).normal(),
|
||||||
|
"chartreuse3b" | "xterm_chartreuse3b" => Color::Fixed(76).normal(),
|
||||||
|
"palegreen3a" | "xterm_palegreen3a" => Color::Fixed(77).normal(),
|
||||||
|
"seagreen3" | "xterm_seagreen3" => Color::Fixed(78).normal(),
|
||||||
|
"aquamarine3" | "xterm_aquamarine3" => Color::Fixed(79).normal(),
|
||||||
|
"mediumturquoise" | "xterm_mediumturquoise" => Color::Fixed(80).normal(),
|
||||||
|
"steelblue1b" | "xterm_steelblue1b" => Color::Fixed(81).normal(),
|
||||||
|
"chartreuse2a" | "xterm_chartreuse2a" => Color::Fixed(82).normal(),
|
||||||
|
"seagreen2" | "xterm_seagreen2" => Color::Fixed(83).normal(),
|
||||||
|
"seagreen1a" | "xterm_seagreen1a" => Color::Fixed(84).normal(),
|
||||||
|
"seagreen1b" | "xterm_seagreen1b" => Color::Fixed(85).normal(),
|
||||||
|
"aquamarine1a" | "xterm_aquamarine1a" => Color::Fixed(86).normal(),
|
||||||
|
"darkslategray2" | "xterm_darkslategray2" => Color::Fixed(87).normal(),
|
||||||
|
"darkredb" | "xterm_darkredb" => Color::Fixed(88).normal(),
|
||||||
|
"deeppink4b" | "xterm_deeppink4b" => Color::Fixed(89).normal(),
|
||||||
|
"darkmagentaa" | "xterm_darkmagentaa" => Color::Fixed(90).normal(),
|
||||||
|
"darkmagentab" | "xterm_darkmagentab" => Color::Fixed(91).normal(),
|
||||||
|
"darkvioleta" | "xterm_darkvioleta" => Color::Fixed(92).normal(),
|
||||||
|
"xpurpleb" | "xterm_purpleb" => Color::Fixed(93).normal(),
|
||||||
|
"orange4b" | "xterm_orange4b" => Color::Fixed(94).normal(),
|
||||||
|
"lightpink4" | "xterm_lightpink4" => Color::Fixed(95).normal(),
|
||||||
|
"plum4" | "xterm_plum4" => Color::Fixed(96).normal(),
|
||||||
|
"mediumpurple3a" | "xterm_mediumpurple3a" => Color::Fixed(97).normal(),
|
||||||
|
"mediumpurple3b" | "xterm_mediumpurple3b" => Color::Fixed(98).normal(),
|
||||||
|
"slateblue1" | "xterm_slateblue1" => Color::Fixed(99).normal(),
|
||||||
|
"yellow4a" | "xterm_yellow4a" => Color::Fixed(100).normal(),
|
||||||
|
"wheat4" | "xterm_wheat4" => Color::Fixed(101).normal(),
|
||||||
|
"grey53" | "xterm_grey53" => Color::Fixed(102).normal(),
|
||||||
|
"lightslategrey" | "xterm_lightslategrey" => Color::Fixed(103).normal(),
|
||||||
|
"mediumpurple" | "xterm_mediumpurple" => Color::Fixed(104).normal(),
|
||||||
|
"lightslateblue" | "xterm_lightslateblue" => Color::Fixed(105).normal(),
|
||||||
|
"yellow4b" | "xterm_yellow4b" => Color::Fixed(106).normal(),
|
||||||
|
"darkolivegreen3a" | "xterm_darkolivegreen3a" => Color::Fixed(107).normal(),
|
||||||
|
"darkseagreen" | "xterm_darkseagreen" => Color::Fixed(108).normal(),
|
||||||
|
"lightskyblue3a" | "xterm_lightskyblue3a" => Color::Fixed(109).normal(),
|
||||||
|
"lightskyblue3b" | "xterm_lightskyblue3b" => Color::Fixed(110).normal(),
|
||||||
|
"skyblue2" | "xterm_skyblue2" => Color::Fixed(111).normal(),
|
||||||
|
"chartreuse2b" | "xterm_chartreuse2b" => Color::Fixed(112).normal(),
|
||||||
|
"darkolivegreen3b" | "xterm_darkolivegreen3b" => Color::Fixed(113).normal(),
|
||||||
|
"palegreen3b" | "xterm_palegreen3b" => Color::Fixed(114).normal(),
|
||||||
|
"darkseagreen3a" | "xterm_darkseagreen3a" => Color::Fixed(115).normal(),
|
||||||
|
"darkslategray3" | "xterm_darkslategray3" => Color::Fixed(116).normal(),
|
||||||
|
"skyblue1" | "xterm_skyblue1" => Color::Fixed(117).normal(),
|
||||||
|
"chartreuse1" | "xterm_chartreuse1" => Color::Fixed(118).normal(),
|
||||||
|
"lightgreena" | "xterm_lightgreena" => Color::Fixed(119).normal(),
|
||||||
|
"lightgreenb" | "xterm_lightgreenb" => Color::Fixed(120).normal(),
|
||||||
|
"palegreen1a" | "xterm_palegreen1a" => Color::Fixed(121).normal(),
|
||||||
|
"aquamarine1b" | "xterm_aquamarine1b" => Color::Fixed(122).normal(),
|
||||||
|
"darkslategray1" | "xterm_darkslategray1" => Color::Fixed(123).normal(),
|
||||||
|
"red3a" | "xterm_red3a" => Color::Fixed(124).normal(),
|
||||||
|
"deeppink4c" | "xterm_deeppink4c" => Color::Fixed(125).normal(),
|
||||||
|
"mediumvioletred" | "xterm_mediumvioletred" => Color::Fixed(126).normal(),
|
||||||
|
"magenta3" | "xterm_magenta3" => Color::Fixed(127).normal(),
|
||||||
|
"darkvioletb" | "xterm_darkvioletb" => Color::Fixed(128).normal(),
|
||||||
|
"purplec" | "xterm_purplec" => Color::Fixed(129).normal(),
|
||||||
|
"darkorange3a" | "xterm_darkorange3a" => Color::Fixed(130).normal(),
|
||||||
|
"indianreda" | "xterm_indianreda" => Color::Fixed(131).normal(),
|
||||||
|
"hotpink3a" | "xterm_hotpink3a" => Color::Fixed(132).normal(),
|
||||||
|
"mediumorchid3" | "xterm_mediumorchid3" => Color::Fixed(133).normal(),
|
||||||
|
"mediumorchid" | "xterm_mediumorchid" => Color::Fixed(134).normal(),
|
||||||
|
"mediumpurple2a" | "xterm_mediumpurple2a" => Color::Fixed(135).normal(),
|
||||||
|
"darkgoldenrod" | "xterm_darkgoldenrod" => Color::Fixed(136).normal(),
|
||||||
|
"lightsalmon3a" | "xterm_lightsalmon3a" => Color::Fixed(137).normal(),
|
||||||
|
"rosybrown" | "xterm_rosybrown" => Color::Fixed(138).normal(),
|
||||||
|
"grey63" | "xterm_grey63" => Color::Fixed(139).normal(),
|
||||||
|
"mediumpurple2b" | "xterm_mediumpurple2b" => Color::Fixed(140).normal(),
|
||||||
|
"mediumpurple1" | "xterm_mediumpurple1" => Color::Fixed(141).normal(),
|
||||||
|
"gold3a" | "xterm_gold3a" => Color::Fixed(142).normal(),
|
||||||
|
"darkkhaki" | "xterm_darkkhaki" => Color::Fixed(143).normal(),
|
||||||
|
"navajowhite3" | "xterm_navajowhite3" => Color::Fixed(144).normal(),
|
||||||
|
"grey69" | "xterm_grey69" => Color::Fixed(145).normal(),
|
||||||
|
"lightsteelblue3" | "xterm_lightsteelblue3" => Color::Fixed(146).normal(),
|
||||||
|
"lightsteelblue" | "xterm_lightsteelblue" => Color::Fixed(147).normal(),
|
||||||
|
"yellow3a" | "xterm_yellow3a" => Color::Fixed(148).normal(),
|
||||||
|
"darkolivegreen3c" | "xterm_darkolivegreen3c" => Color::Fixed(149).normal(),
|
||||||
|
"darkseagreen3b" | "xterm_darkseagreen3b" => Color::Fixed(150).normal(),
|
||||||
|
"darkseagreen2a" | "xterm_darkseagreen2a" => Color::Fixed(151).normal(),
|
||||||
|
"lightcyan3" | "xterm_lightcyan3" => Color::Fixed(152).normal(),
|
||||||
|
"lightskyblue1" | "xterm_lightskyblue1" => Color::Fixed(153).normal(),
|
||||||
|
"greenyellow" | "xterm_greenyellow" => Color::Fixed(154).normal(),
|
||||||
|
"darkolivegreen2" | "xterm_darkolivegreen2" => Color::Fixed(155).normal(),
|
||||||
|
"palegreen1b" | "xterm_palegreen1b" => Color::Fixed(156).normal(),
|
||||||
|
"darkseagreen2b" | "xterm_darkseagreen2b" => Color::Fixed(157).normal(),
|
||||||
|
"darkseagreen1a" | "xterm_darkseagreen1a" => Color::Fixed(158).normal(),
|
||||||
|
"paleturquoise1" | "xterm_paleturquoise1" => Color::Fixed(159).normal(),
|
||||||
|
"red3b" | "xterm_red3b" => Color::Fixed(160).normal(),
|
||||||
|
"deeppink3a" | "xterm_deeppink3a" => Color::Fixed(161).normal(),
|
||||||
|
"deeppink3b" | "xterm_deeppink3b" => Color::Fixed(162).normal(),
|
||||||
|
"magenta3a" | "xterm_magenta3a" => Color::Fixed(163).normal(),
|
||||||
|
"magenta3b" | "xterm_magenta3b" => Color::Fixed(164).normal(),
|
||||||
|
"magenta2a" | "xterm_magenta2a" => Color::Fixed(165).normal(),
|
||||||
|
"darkorange3b" | "xterm_darkorange3b" => Color::Fixed(166).normal(),
|
||||||
|
"indianredb" | "xterm_indianredb" => Color::Fixed(167).normal(),
|
||||||
|
"hotpink3b" | "xterm_hotpink3b" => Color::Fixed(168).normal(),
|
||||||
|
"hotpink2" | "xterm_hotpink2" => Color::Fixed(169).normal(),
|
||||||
|
"orchid" | "xterm_orchid" => Color::Fixed(170).normal(),
|
||||||
|
"mediumorchid1a" | "xterm_mediumorchid1a" => Color::Fixed(171).normal(),
|
||||||
|
"orange3" | "xterm_orange3" => Color::Fixed(172).normal(),
|
||||||
|
"lightsalmon3b" | "xterm_lightsalmon3b" => Color::Fixed(173).normal(),
|
||||||
|
"lightpink3" | "xterm_lightpink3" => Color::Fixed(174).normal(),
|
||||||
|
"pink3" | "xterm_pink3" => Color::Fixed(175).normal(),
|
||||||
|
"plum3" | "xterm_plum3" => Color::Fixed(176).normal(),
|
||||||
|
"violet" | "xterm_violet" => Color::Fixed(177).normal(),
|
||||||
|
"gold3b" | "xterm_gold3b" => Color::Fixed(178).normal(),
|
||||||
|
"lightgoldenrod3" | "xterm_lightgoldenrod3" => Color::Fixed(179).normal(),
|
||||||
|
"tan" | "xterm_tan" => Color::Fixed(180).normal(),
|
||||||
|
"mistyrose3" | "xterm_mistyrose3" => Color::Fixed(181).normal(),
|
||||||
|
"thistle3" | "xterm_thistle3" => Color::Fixed(182).normal(),
|
||||||
|
"plum2" | "xterm_plum2" => Color::Fixed(183).normal(),
|
||||||
|
"yellow3b" | "xterm_yellow3b" => Color::Fixed(184).normal(),
|
||||||
|
"khaki3" | "xterm_khaki3" => Color::Fixed(185).normal(),
|
||||||
|
"lightgoldenrod2" | "xterm_lightgoldenrod2" => Color::Fixed(186).normal(),
|
||||||
|
"lightyellow3" | "xterm_lightyellow3" => Color::Fixed(187).normal(),
|
||||||
|
"grey84" | "xterm_grey84" => Color::Fixed(188).normal(),
|
||||||
|
"lightsteelblue1" | "xterm_lightsteelblue1" => Color::Fixed(189).normal(),
|
||||||
|
"yellow2" | "xterm_yellow2" => Color::Fixed(190).normal(),
|
||||||
|
"darkolivegreen1a" | "xterm_darkolivegreen1a" => Color::Fixed(191).normal(),
|
||||||
|
"darkolivegreen1b" | "xterm_darkolivegreen1b" => Color::Fixed(192).normal(),
|
||||||
|
"darkseagreen1b" | "xterm_darkseagreen1b" => Color::Fixed(193).normal(),
|
||||||
|
"honeydew2" | "xterm_honeydew2" => Color::Fixed(194).normal(),
|
||||||
|
"lightcyan1" | "xterm_lightcyan1" => Color::Fixed(195).normal(),
|
||||||
|
"red1" | "xterm_red1" => Color::Fixed(196).normal(),
|
||||||
|
"deeppink2" | "xterm_deeppink2" => Color::Fixed(197).normal(),
|
||||||
|
"deeppink1a" | "xterm_deeppink1a" => Color::Fixed(198).normal(),
|
||||||
|
"deeppink1b" | "xterm_deeppink1b" => Color::Fixed(199).normal(),
|
||||||
|
"magenta2b" | "xterm_magenta2b" => Color::Fixed(200).normal(),
|
||||||
|
"magenta1" | "xterm_magenta1" => Color::Fixed(201).normal(),
|
||||||
|
"orangered1" | "xterm_orangered1" => Color::Fixed(202).normal(),
|
||||||
|
"indianred1a" | "xterm_indianred1a" => Color::Fixed(203).normal(),
|
||||||
|
"indianred1b" | "xterm_indianred1b" => Color::Fixed(204).normal(),
|
||||||
|
"hotpinka" | "xterm_hotpinka" => Color::Fixed(205).normal(),
|
||||||
|
"hotpinkb" | "xterm_hotpinkb" => Color::Fixed(206).normal(),
|
||||||
|
"mediumorchid1b" | "xterm_mediumorchid1b" => Color::Fixed(207).normal(),
|
||||||
|
"darkorange" | "xterm_darkorange" => Color::Fixed(208).normal(),
|
||||||
|
"salmon1" | "xterm_salmon1" => Color::Fixed(209).normal(),
|
||||||
|
"lightcoral" | "xterm_lightcoral" => Color::Fixed(210).normal(),
|
||||||
|
"palevioletred1" | "xterm_palevioletred1" => Color::Fixed(211).normal(),
|
||||||
|
"orchid2" | "xterm_orchid2" => Color::Fixed(212).normal(),
|
||||||
|
"orchid1" | "xterm_orchid1" => Color::Fixed(213).normal(),
|
||||||
|
"orange1" | "xterm_orange1" => Color::Fixed(214).normal(),
|
||||||
|
"sandybrown" | "xterm_sandybrown" => Color::Fixed(215).normal(),
|
||||||
|
"lightsalmon1" | "xterm_lightsalmon1" => Color::Fixed(216).normal(),
|
||||||
|
"lightpink1" | "xterm_lightpink1" => Color::Fixed(217).normal(),
|
||||||
|
"pink1" | "xterm_pink1" => Color::Fixed(218).normal(),
|
||||||
|
"plum1" | "xterm_plum1" => Color::Fixed(219).normal(),
|
||||||
|
"gold1" | "xterm_gold1" => Color::Fixed(220).normal(),
|
||||||
|
"lightgoldenrod2a" | "xterm_lightgoldenrod2a" => Color::Fixed(221).normal(),
|
||||||
|
"lightgoldenrod2b" | "xterm_lightgoldenrod2b" => Color::Fixed(222).normal(),
|
||||||
|
"navajowhite1" | "xterm_navajowhite1" => Color::Fixed(223).normal(),
|
||||||
|
"mistyrose1" | "xterm_mistyrose1" => Color::Fixed(224).normal(),
|
||||||
|
"thistle1" | "xterm_thistle1" => Color::Fixed(225).normal(),
|
||||||
|
"yellow1" | "xterm_yellow1" => Color::Fixed(226).normal(),
|
||||||
|
"lightgoldenrod1" | "xterm_lightgoldenrod1" => Color::Fixed(227).normal(),
|
||||||
|
"khaki1" | "xterm_khaki1" => Color::Fixed(228).normal(),
|
||||||
|
"wheat1" | "xterm_wheat1" => Color::Fixed(229).normal(),
|
||||||
|
"cornsilk1" | "xterm_cornsilk1" => Color::Fixed(230).normal(),
|
||||||
|
"grey100" | "xterm_grey100" => Color::Fixed(231).normal(),
|
||||||
|
"grey3" | "xterm_grey3" => Color::Fixed(232).normal(),
|
||||||
|
"grey7" | "xterm_grey7" => Color::Fixed(233).normal(),
|
||||||
|
"grey11" | "xterm_grey11" => Color::Fixed(234).normal(),
|
||||||
|
"grey15" | "xterm_grey15" => Color::Fixed(235).normal(),
|
||||||
|
"grey19" | "xterm_grey19" => Color::Fixed(236).normal(),
|
||||||
|
"grey23" | "xterm_grey23" => Color::Fixed(237).normal(),
|
||||||
|
"grey27" | "xterm_grey27" => Color::Fixed(238).normal(),
|
||||||
|
"grey30" | "xterm_grey30" => Color::Fixed(239).normal(),
|
||||||
|
"grey35" | "xterm_grey35" => Color::Fixed(240).normal(),
|
||||||
|
"grey39" | "xterm_grey39" => Color::Fixed(241).normal(),
|
||||||
|
"grey42" | "xterm_grey42" => Color::Fixed(242).normal(),
|
||||||
|
"grey46" | "xterm_grey46" => Color::Fixed(243).normal(),
|
||||||
|
"grey50" | "xterm_grey50" => Color::Fixed(244).normal(),
|
||||||
|
"grey54" | "xterm_grey54" => Color::Fixed(245).normal(),
|
||||||
|
"grey58" | "xterm_grey58" => Color::Fixed(246).normal(),
|
||||||
|
"grey62" | "xterm_grey62" => Color::Fixed(247).normal(),
|
||||||
|
"grey66" | "xterm_grey66" => Color::Fixed(248).normal(),
|
||||||
|
"grey70" | "xterm_grey70" => Color::Fixed(249).normal(),
|
||||||
|
"grey74" | "xterm_grey74" => Color::Fixed(250).normal(),
|
||||||
|
"grey78" | "xterm_grey78" => Color::Fixed(251).normal(),
|
||||||
|
"grey82" | "xterm_grey82" => Color::Fixed(252).normal(),
|
||||||
|
"grey85" | "xterm_grey85" => Color::Fixed(253).normal(),
|
||||||
|
"grey89" | "xterm_grey89" => Color::Fixed(254).normal(),
|
||||||
|
"grey93" | "xterm_grey93" => Color::Fixed(255).normal(),
|
||||||
|
_ => Color::White.normal(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_color(s: &str) -> Option<Color> {
|
||||||
|
let color = match s {
|
||||||
|
"g" | "green" => Color::Green,
|
||||||
|
"lg" | "light_green" => Color::LightGreen,
|
||||||
|
"r" | "red" => Color::Red,
|
||||||
|
"lr" | "light_red" => Color::LightRed,
|
||||||
|
"u" | "blue" => Color::Blue,
|
||||||
|
"lu" | "light_blue" => Color::LightBlue,
|
||||||
|
"b" | "black" => Color::Black,
|
||||||
|
"ligr" | "light_gray" => Color::LightGray,
|
||||||
|
"y" | "yellow" => Color::Yellow,
|
||||||
|
"ly" | "light_yellow" => Color::LightYellow,
|
||||||
|
"p" | "purple" => Color::Purple,
|
||||||
|
"lp" | "light_purple" => Color::LightPurple,
|
||||||
|
"c" | "cyan" => Color::Cyan,
|
||||||
|
"lc" | "light_cyan" => Color::LightCyan,
|
||||||
|
"w" | "white" => Color::White,
|
||||||
|
"dgr" | "dark_gray" => Color::DarkGray,
|
||||||
|
"def" | "default" => Color::Default,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_modifiers(attrs: &str, style: &mut Style) {
|
||||||
|
// setup the attributes available in nu_ansi_term::Style
|
||||||
|
//
|
||||||
|
// since we can combine styles like bold-italic, iterate through the chars
|
||||||
|
// and set the bools for later use in the nu_ansi_term::Style application
|
||||||
|
for ch in attrs.to_lowercase().chars() {
|
||||||
|
match ch {
|
||||||
|
'l' => style.is_blink = true,
|
||||||
|
'b' => style.is_bold = true,
|
||||||
|
'd' => style.is_dimmed = true,
|
||||||
|
'h' => style.is_hidden = true,
|
||||||
|
'i' => style.is_italic = true,
|
||||||
|
'r' => style.is_reverse = true,
|
||||||
|
's' => style.is_strikethrough = true,
|
||||||
|
'u' => style.is_underline = true,
|
||||||
|
'n' => (),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_color_str(s: &str) -> Option<Color> {
|
||||||
|
if s.starts_with('#') {
|
||||||
|
color_from_hex(s).ok().and_then(|c| c)
|
||||||
|
} else {
|
||||||
|
lookup_color(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,41 +1,56 @@
|
|||||||
use crate::color_config::lookup_ansi_color_style;
|
use crate::{color_config::lookup_ansi_color_style, color_record_to_nustyle};
|
||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use nu_protocol::Config;
|
use nu_protocol::{Config, Value};
|
||||||
|
|
||||||
|
// The default colors for shapes, used when there is no config for them.
|
||||||
|
pub fn default_shape_color(shape: String) -> Style {
|
||||||
|
match shape.as_ref() {
|
||||||
|
"shape_and" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_bool" => Style::new().fg(Color::LightCyan),
|
||||||
|
"shape_custom" => Style::new().fg(Color::Green),
|
||||||
|
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_directory" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_external" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
|
||||||
|
"shape_filepath" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
||||||
|
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_list" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_literal" => Style::new().fg(Color::Blue),
|
||||||
|
"shape_nothing" => Style::new().fg(Color::LightCyan),
|
||||||
|
"shape_operator" => Style::new().fg(Color::Yellow),
|
||||||
|
"shape_or" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_pipe" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
||||||
|
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_redirection" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_signature" => Style::new().fg(Color::Green).bold(),
|
||||||
|
"shape_string" => Style::new().fg(Color::Green),
|
||||||
|
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_variable" => Style::new().fg(Color::Purple),
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
||||||
match conf.color_config.get(shape.as_str()) {
|
match conf.color_config.get(shape.as_str()) {
|
||||||
Some(int_color) => match int_color.as_string() {
|
Some(int_color) => {
|
||||||
Ok(int_color) => lookup_ansi_color_style(&int_color),
|
// Shapes do not use color_config closures, currently.
|
||||||
Err(_) => Style::default(),
|
match int_color {
|
||||||
},
|
Value::Record { .. } => color_record_to_nustyle(int_color),
|
||||||
None => match shape.as_ref() {
|
Value::String { val, .. } => lookup_ansi_color_style(val),
|
||||||
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
// Defer to the default in the event of incorrect types being given
|
||||||
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
// (i.e. treat null, etc. as the value being unset)
|
||||||
"shape_bool" => Style::new().fg(Color::LightCyan),
|
_ => default_shape_color(shape),
|
||||||
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
}
|
||||||
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
}
|
||||||
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
None => default_shape_color(shape),
|
||||||
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_external" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
|
|
||||||
"shape_literal" => Style::new().fg(Color::Blue),
|
|
||||||
"shape_operator" => Style::new().fg(Color::Yellow),
|
|
||||||
"shape_signature" => Style::new().fg(Color::Green).bold(),
|
|
||||||
"shape_string" => Style::new().fg(Color::Green),
|
|
||||||
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_list" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_filepath" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_directory" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_variable" => Style::new().fg(Color::Purple),
|
|
||||||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_custom" => Style::new().fg(Color::Green),
|
|
||||||
"shape_nothing" => Style::new().fg(Color::LightCyan),
|
|
||||||
_ => Style::default(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
285
crates/nu-color-config/src/style_computer.rs
Normal file
285
crates/nu-color-config/src/style_computer.rs
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle};
|
||||||
|
use nu_ansi_term::{Color, Style};
|
||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
CliError, IntoPipelineData, Value,
|
||||||
|
};
|
||||||
|
use tabled::alignment::AlignmentHorizontal;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::{Debug, Formatter, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ComputableStyle represents the valid user style types: a single color value, or a closure which
|
||||||
|
// takes an input value and produces a color value. The latter represents a value which
|
||||||
|
// is computed at use-time.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ComputableStyle {
|
||||||
|
Static(Style),
|
||||||
|
Closure(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
// macro used for adding initial values to the style hashmap
|
||||||
|
macro_rules! initial {
|
||||||
|
($a:expr, $b:expr) => {
|
||||||
|
($a.to_string(), ComputableStyle::Static($b))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// An alias for the mapping used internally by StyleComputer.
|
||||||
|
pub type StyleMapping = HashMap<String, ComputableStyle>;
|
||||||
|
//
|
||||||
|
// A StyleComputer is an all-in-one way to compute styles. A nu command can
|
||||||
|
// simply create it with from_config(), and then use it with compute().
|
||||||
|
// It stores the engine state and stack needed to run closures that
|
||||||
|
// may be defined as a user style.
|
||||||
|
//
|
||||||
|
pub struct StyleComputer<'a> {
|
||||||
|
engine_state: &'a EngineState,
|
||||||
|
stack: &'a Stack,
|
||||||
|
map: StyleMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StyleComputer<'a> {
|
||||||
|
// This is NOT meant to be used in most cases - please use from_config() instead.
|
||||||
|
// This only exists for testing purposes.
|
||||||
|
pub fn new(
|
||||||
|
engine_state: &'a EngineState,
|
||||||
|
stack: &'a Stack,
|
||||||
|
map: StyleMapping,
|
||||||
|
) -> StyleComputer<'a> {
|
||||||
|
StyleComputer {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The main method. Takes a string name which maps to a color_config style name,
|
||||||
|
// and a Nu value to pipe into any closures that may have been defined there.
|
||||||
|
pub fn compute(&self, style_name: &str, value: &Value) -> Style {
|
||||||
|
match self.map.get(style_name) {
|
||||||
|
// Static values require no computation.
|
||||||
|
Some(ComputableStyle::Static(s)) => *s,
|
||||||
|
// Closures are run here.
|
||||||
|
Some(ComputableStyle::Closure(Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
captures,
|
||||||
|
span,
|
||||||
|
})) => {
|
||||||
|
let block = self.engine_state.get_block(*block_id).clone();
|
||||||
|
// Because captures_to_stack() clones, we don't need to use with_env() here
|
||||||
|
// (contrast with_env() usage in `each` or `do`).
|
||||||
|
let mut stack = self.stack.captures_to_stack(captures);
|
||||||
|
|
||||||
|
// Support 1-argument blocks as well as 0-argument blocks.
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack.add_var(*var_id, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the block.
|
||||||
|
match eval_block(
|
||||||
|
self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
&block,
|
||||||
|
value.clone().into_pipeline_data(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
Ok(v) => {
|
||||||
|
let value = v.into_value(*span);
|
||||||
|
// These should be the same color data forms supported by color_config.
|
||||||
|
match value {
|
||||||
|
Value::Record { .. } => color_record_to_nustyle(&value),
|
||||||
|
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is basically a copy of nu_cli::report_error(), but that isn't usable due to
|
||||||
|
// dependencies. While crudely spitting out a bunch of errors like this is not ideal,
|
||||||
|
// currently hook closure errors behave roughly the same.
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Error: {:?}",
|
||||||
|
CliError(&e, &StateWorkingSet::new(self.engine_state))
|
||||||
|
);
|
||||||
|
Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There should be no other kinds of values (due to create_map() in config.rs filtering them out)
|
||||||
|
// so this is just a fallback.
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used only by the `table` command.
|
||||||
|
pub fn style_primitive(&self, value: &Value) -> TextStyle {
|
||||||
|
let s = self.compute(&value.get_type().to_string(), value);
|
||||||
|
match *value {
|
||||||
|
Value::Bool { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Int { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Filesize { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Duration { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Date { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Range { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Float { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::String { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Nothing { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Binary { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::CellPath { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Record { .. } | Value::List { .. } | Value::Block { .. } => {
|
||||||
|
TextStyle::with_style(AlignmentHorizontal::Left, s)
|
||||||
|
}
|
||||||
|
_ => TextStyle::basic_left(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main constructor.
|
||||||
|
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
|
// Create the hashmap
|
||||||
|
let mut map: StyleMapping = HashMap::from([
|
||||||
|
initial!("separator", Color::White.normal()),
|
||||||
|
initial!(
|
||||||
|
"leading_trailing_space_bg",
|
||||||
|
Style::default().on(Color::Rgb(128, 128, 128))
|
||||||
|
),
|
||||||
|
initial!("header", Color::White.normal()),
|
||||||
|
initial!("empty", Color::White.normal()),
|
||||||
|
initial!("bool", Color::White.normal()),
|
||||||
|
initial!("int", Color::White.normal()),
|
||||||
|
initial!("filesize", Color::White.normal()),
|
||||||
|
initial!("duration", Color::White.normal()),
|
||||||
|
initial!("date", Color::White.normal()),
|
||||||
|
initial!("range", Color::White.normal()),
|
||||||
|
initial!("float", Color::White.normal()),
|
||||||
|
initial!("string", Color::White.normal()),
|
||||||
|
initial!("nothing", Color::White.normal()),
|
||||||
|
initial!("binary", Color::White.normal()),
|
||||||
|
initial!("cellpath", Color::White.normal()),
|
||||||
|
initial!("row_index", Color::Green.bold()),
|
||||||
|
initial!("record", Color::White.normal()),
|
||||||
|
initial!("list", Color::White.normal()),
|
||||||
|
initial!("block", Color::White.normal()),
|
||||||
|
initial!("hints", Color::DarkGray.normal()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (key, value) in &config.color_config {
|
||||||
|
match value {
|
||||||
|
Value::Closure { .. } => {
|
||||||
|
map.insert(key.to_string(), ComputableStyle::Closure(value.clone()));
|
||||||
|
}
|
||||||
|
Value::Record { .. } => {
|
||||||
|
map.insert(
|
||||||
|
key.to_string(),
|
||||||
|
ComputableStyle::Static(color_record_to_nustyle(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
// update the stylemap with the found key
|
||||||
|
let color = lookup_ansi_color_style(val.as_str());
|
||||||
|
if let Some(v) = map.get_mut(key) {
|
||||||
|
*v = ComputableStyle::Static(color);
|
||||||
|
} else {
|
||||||
|
map.insert(key.to_string(), ComputableStyle::Static(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never occur.
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleComputer::new(engine_state, stack, map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because EngineState doesn't have Debug (Dec 2022),
|
||||||
|
// this incomplete representation must be used.
|
||||||
|
impl<'a> Debug for StyleComputer<'a> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
|
f.debug_struct("StyleComputer")
|
||||||
|
.field("map", &self.map)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_static() {
|
||||||
|
use nu_protocol::Span;
|
||||||
|
|
||||||
|
let style1 = Style::default().italic();
|
||||||
|
let style2 = Style::default().underline();
|
||||||
|
// Create a "dummy" style_computer for this test.
|
||||||
|
let dummy_engine_state = EngineState::new();
|
||||||
|
let mut dummy_stack = Stack::new();
|
||||||
|
let style_computer = StyleComputer::new(
|
||||||
|
&dummy_engine_state,
|
||||||
|
&mut dummy_stack,
|
||||||
|
HashMap::from([
|
||||||
|
("string".into(), ComputableStyle::Static(style1)),
|
||||||
|
("row_index".into(), ComputableStyle::Static(style2)),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
style_computer.compute("string", &Value::nothing(Span::unknown())),
|
||||||
|
style1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
style_computer.compute("row_index", &Value::nothing(Span::unknown())),
|
||||||
|
style2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because each closure currently runs in a separate environment, checks that the closures have run
|
||||||
|
// must use the filesystem.
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_closure_basic() {
|
||||||
|
use nu_test_support::{nu, nu_repl_code, playground::Playground};
|
||||||
|
Playground::setup("computable_style_closure_basic", |dirs, _| {
|
||||||
|
let inp = [
|
||||||
|
r#"let-env config = {
|
||||||
|
color_config: {
|
||||||
|
string: {|e| touch ($e + '.obj'); 'red' }
|
||||||
|
}
|
||||||
|
};"#,
|
||||||
|
"[bell book candle] | table | ignore",
|
||||||
|
"ls | get name | to nuon",
|
||||||
|
];
|
||||||
|
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
|
||||||
|
assert_eq!(actual_repl.err, "");
|
||||||
|
assert_eq!(actual_repl.out, "[bell.obj, book.obj, candle.obj]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_closure_errors() {
|
||||||
|
use nu_test_support::{nu, nu_repl_code};
|
||||||
|
let inp = [
|
||||||
|
r#"let-env config = {
|
||||||
|
color_config: {
|
||||||
|
string: {|e| $e + 2 }
|
||||||
|
}
|
||||||
|
};"#,
|
||||||
|
"[bell] | table",
|
||||||
|
];
|
||||||
|
let actual_repl = nu!(cwd: ".", nu_repl_code(&inp));
|
||||||
|
// Check that the error was printed
|
||||||
|
assert!(actual_repl.err.contains("type mismatch for operator"));
|
||||||
|
// Check that the value was printed
|
||||||
|
assert!(actual_repl.out.contains("bell"));
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
use crate::wrap::Alignment;
|
|
||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub type Alignment = tabled::alignment::AlignmentHorizontal;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
@ -239,18 +241,22 @@ impl Default for TextStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl tabled::papergrid::Color for TextStyle {
|
||||||
pub struct StyledString {
|
fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub contents: String,
|
if let Some(color) = &self.color_style {
|
||||||
pub style: TextStyle,
|
color.prefix().fmt(f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledString {
|
Ok(())
|
||||||
pub fn new(contents: String, style: TextStyle) -> StyledString {
|
|
||||||
StyledString { contents, style }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: TextStyle) {
|
fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.style = style;
|
if let Some(color) = &self.color_style {
|
||||||
|
if !color.is_plain() {
|
||||||
|
f.write_str("\u{1b}[0m")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,45 +1,50 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Nushell's built-in commands"
|
description = "Nushell's built-in commands"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.63.0"
|
version = "0.74.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.63.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.74.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.63.0" }
|
nu-engine = { path = "../nu-engine", version = "0.74.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.63.0" }
|
nu-glob = { path = "../nu-glob", version = "0.74.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.63.0" }
|
nu-json = { path = "../nu-json", version = "0.74.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.63.0" }
|
nu-parser = { path = "../nu-parser", version = "0.74.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.63.0" }
|
nu-path = { path = "../nu-path", version = "0.74.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.63.0" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.74.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.74.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.63.0" }
|
nu-system = { path = "../nu-system", version = "0.74.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.63.0" }
|
nu-table = { path = "../nu-table", version = "0.74.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.63.0" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.74.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.63.0" }
|
nu-utils = { path = "../nu-utils", version = "0.74.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.63.0" }
|
nu-explore = { path = "../nu-explore", version = "0.74.0" }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
|
num-format = { version = "0.4.3" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
|
atty = "0.2.14"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
byteorder = "1.4.3"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
calamine = "0.18.0"
|
calamine = "0.19.1"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.23", features = ["unstable-locales", "std"], default-features = false }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
chrono-tz = "0.6.1"
|
chrono-tz = "0.6.3"
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
dialoguer = "0.9.0"
|
dialoguer = { default-features = false, version = "0.9.0" }
|
||||||
digest = "0.10.0"
|
digest = { default-features = false, version = "0.10.0" }
|
||||||
dtparse = "1.2.0"
|
dtparse = "1.2.0"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
encoding_rs = "0.8.30"
|
encoding_rs = "0.8.30"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
filetime = "0.2.15"
|
filetime = "0.2.15"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
@ -47,78 +52,117 @@ htmlescape = "0.3.1"
|
|||||||
ical = "0.7.0"
|
ical = "0.7.0"
|
||||||
indexmap = { version="1.7", features=["serde-1"] }
|
indexmap = { version="1.7", features=["serde-1"] }
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
|
is-root = "0.1.2"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
meval = "0.2.0"
|
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
|
mime_guess = "2.0.4"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
num = { version = "0.4.0", optional = true }
|
num = { version = "0.4.0", optional = true }
|
||||||
|
num-traits = "0.2.14"
|
||||||
|
once_cell = "1.0"
|
||||||
|
open = "3.2.0"
|
||||||
pathdiff = "0.2.1"
|
pathdiff = "0.2.1"
|
||||||
powierza-coefficient = "1.0"
|
powierza-coefficient = "1.0.2"
|
||||||
quick-xml = "0.22"
|
quick-xml = "0.25"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
regex = "1.5.4"
|
regex = "1.6.0"
|
||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||||
roxmltree = "0.14.0"
|
roxmltree = "0.16.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
same-file = "1.0.6"
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8.16"
|
serde_yaml = "0.9.4"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
strip-ansi-escapes = "0.1.1"
|
sysinfo = "0.26.2"
|
||||||
sysinfo = "0.23.5"
|
terminal_size = "0.2.1"
|
||||||
terminal_size = "0.1.17"
|
thiserror = "1.0.31"
|
||||||
thiserror = "1.0.29"
|
titlecase = "2.0.0"
|
||||||
titlecase = "1.1.0"
|
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
unicode-segmentation = "1.8.0"
|
unicode-segmentation = "1.8.0"
|
||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
percent-encoding = "2.2.0"
|
||||||
which = { version = "4.2.2", optional = true }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
reedline = { version = "0.6.0", features = ["bashisms"]}
|
which = { version = "4.3.0", optional = true }
|
||||||
wax = { version = "0.4.0", features = ["diagnostics"] }
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
wax = { version = "0.5.0" }
|
||||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
|
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "2.0.0"
|
umask = "2.0.0"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
version = "2.1.3"
|
version = "3.0.0"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
version = "0.21.1"
|
version = "0.25.0"
|
||||||
# path = "../../../../polars/polars"
|
|
||||||
optional = true
|
optional = true
|
||||||
features = [
|
features = [
|
||||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
"arg_where",
|
||||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
"checked_arithmetic",
|
||||||
"rolling_window", "strings", "rows", "random",
|
"concat_str",
|
||||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
"cross_join",
|
||||||
"dynamic_groupby"
|
"csv-file",
|
||||||
|
"cum_agg",
|
||||||
|
"default",
|
||||||
|
"dtype-datetime",
|
||||||
|
"dtype-struct",
|
||||||
|
"dtype-categorical",
|
||||||
|
"dynamic_groupby",
|
||||||
|
"ipc",
|
||||||
|
"is_in",
|
||||||
|
"json",
|
||||||
|
"lazy",
|
||||||
|
"object",
|
||||||
|
"parquet",
|
||||||
|
"random",
|
||||||
|
"rolling_window",
|
||||||
|
"rows",
|
||||||
|
"serde",
|
||||||
|
"serde-lazy",
|
||||||
|
"strings",
|
||||||
|
"strings",
|
||||||
|
"to_dummies",
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
|
version = "0.43.0"
|
||||||
|
features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
plugin = ["nu-parser/plugin"]
|
plugin = ["nu-parser/plugin"]
|
||||||
dataframe = ["polars", "num"]
|
dataframe = ["polars", "num", "sqlparser"]
|
||||||
database = ["sqlparser", "rusqlite"]
|
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path = "../nu-test-support", version = "0.74.0" }
|
||||||
|
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
|
proptest = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
@ -3,16 +3,18 @@ use std::process::Command;
|
|||||||
fn main() -> shadow_rs::SdResult<()> {
|
fn main() -> shadow_rs::SdResult<()> {
|
||||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
let hash = get_git_hash().unwrap_or_default();
|
||||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||||
|
|
||||||
shadow_rs::new()
|
shadow_rs::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
fn get_git_hash() -> Option<String> {
|
||||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
Command::new("git")
|
||||||
Ok(String::from_utf8(out.stdout)
|
.args(["rev-parse", "HEAD"])
|
||||||
.expect("could not convert stdout to string")
|
.output()
|
||||||
.trim()
|
.ok()
|
||||||
.to_string())
|
.filter(|output| output.status.success())
|
||||||
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||||
|
.map(|hash| hash.trim().to_string())
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
|
||||||
|
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
|
||||||
|
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
|
||||||
|
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
|
||||||
|
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
|
||||||
|
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
|
||||||
|
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
|
||||||
|
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
|
||||||
|
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
|
||||||
|
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
|
||||||
|
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
|
||||||
|
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
|
||||||
|
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
|
||||||
|
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
|
||||||
|
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
|
||||||
|
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
|
||||||
|
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"
|
105
crates/nu-command/src/bits/and.rs
Normal file
105
crates/nu-command/src/bits/and.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits and"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits and")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit and",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise and for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic and"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits and to two numbers",
|
||||||
|
example: "2 | bits and 2",
|
||||||
|
result: Some(Value::test_int(2)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical and to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits and 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val & target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/bits/bits_.rs
Normal file
49
crates/nu-command/src/bits/bits_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_engine::get_full_help;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Bits;
|
||||||
|
|
||||||
|
impl Command for Bits {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits")
|
||||||
|
.category(Category::Bits)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Various commands for working with bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: get_full_help(
|
||||||
|
&Bits.signature(),
|
||||||
|
&Bits.examples(),
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
99
crates/nu-command/src/bits/mod.rs
Normal file
99
crates/nu-command/src/bits/mod.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
mod and;
|
||||||
|
mod bits_;
|
||||||
|
mod not;
|
||||||
|
mod or;
|
||||||
|
mod rotate_left;
|
||||||
|
mod rotate_right;
|
||||||
|
mod shift_left;
|
||||||
|
mod shift_right;
|
||||||
|
mod xor;
|
||||||
|
|
||||||
|
use nu_protocol::Spanned;
|
||||||
|
|
||||||
|
pub use and::SubCommand as BitsAnd;
|
||||||
|
pub use bits_::Bits;
|
||||||
|
pub use not::SubCommand as BitsNot;
|
||||||
|
pub use or::SubCommand as BitsOr;
|
||||||
|
pub use rotate_left::SubCommand as BitsRotateLeft;
|
||||||
|
pub use rotate_right::SubCommand as BitsRotateRight;
|
||||||
|
pub use shift_left::SubCommand as BitsShiftLeft;
|
||||||
|
pub use shift_right::SubCommand as BitsShiftRight;
|
||||||
|
pub use xor::SubCommand as BitsXor;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum NumberBytes {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
Auto,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum InputNumType {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
SignedOne,
|
||||||
|
SignedTwo,
|
||||||
|
SignedFour,
|
||||||
|
SignedEight,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_number_bytes(number_bytes: &Option<Spanned<String>>) -> NumberBytes {
|
||||||
|
match number_bytes.as_ref() {
|
||||||
|
None => NumberBytes::Eight,
|
||||||
|
Some(size) => match size.item.as_str() {
|
||||||
|
"1" => NumberBytes::One,
|
||||||
|
"2" => NumberBytes::Two,
|
||||||
|
"4" => NumberBytes::Four,
|
||||||
|
"8" => NumberBytes::Eight,
|
||||||
|
"auto" => NumberBytes::Auto,
|
||||||
|
_ => NumberBytes::Invalid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> InputNumType {
|
||||||
|
if signed || val < 0 {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::SignedOne,
|
||||||
|
NumberBytes::Two => InputNumType::SignedTwo,
|
||||||
|
NumberBytes::Four => InputNumType::SignedFour,
|
||||||
|
NumberBytes::Eight => InputNumType::SignedEight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0x7F && val >= -(2i64.pow(7)) {
|
||||||
|
InputNumType::SignedOne
|
||||||
|
} else if val <= 0x7FFF && val >= -(2i64.pow(15)) {
|
||||||
|
InputNumType::SignedTwo
|
||||||
|
} else if val <= 0x7FFFFFFF && val >= -(2i64.pow(31)) {
|
||||||
|
InputNumType::SignedFour
|
||||||
|
} else {
|
||||||
|
InputNumType::SignedEight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::SignedFour,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::One,
|
||||||
|
NumberBytes::Two => InputNumType::Two,
|
||||||
|
NumberBytes::Four => InputNumType::Four,
|
||||||
|
NumberBytes::Eight => InputNumType::Eight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
InputNumType::One
|
||||||
|
} else if val <= 0xFFFF {
|
||||||
|
InputNumType::Two
|
||||||
|
} else if val <= 0xFFFFFFFF {
|
||||||
|
InputNumType::Four
|
||||||
|
} else {
|
||||||
|
InputNumType::Eight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::Four,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
174
crates/nu-command/src/bits/not.rs
Normal file
174
crates/nu-command/src/bits/not.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use super::{get_number_bytes, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits not"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits not")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs logical negation on each bit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["negation"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply logical negation to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits not",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(140737488355323),
|
||||||
|
Value::test_int(140737488355324),
|
||||||
|
Value::test_int(140737488355325),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||||
|
example: "[4 3 2] | bits not -n 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(65531),
|
||||||
|
Value::test_int(65532),
|
||||||
|
Value::test_int(65533),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as signed number",
|
||||||
|
example: "[4 3 2] | bits not -s",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(-5),
|
||||||
|
Value::test_int(-4),
|
||||||
|
Value::test_int(-3),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
if signed || val < 0 {
|
||||||
|
Value::Int { val: !val, span }
|
||||||
|
} else {
|
||||||
|
use NumberBytes::*;
|
||||||
|
let out_val = match number_size {
|
||||||
|
One => !val & 0x00_00_00_00_00_FF,
|
||||||
|
Two => !val & 0x00_00_00_00_FF_FF,
|
||||||
|
Four => !val & 0x00_00_FF_FF_FF_FF,
|
||||||
|
Eight => !val & 0x7F_FF_FF_FF_FF_FF,
|
||||||
|
Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
!val & 0x00_00_00_00_00_FF
|
||||||
|
} else if val <= 0xFF_FF {
|
||||||
|
!val & 0x00_00_00_00_FF_FF
|
||||||
|
} else if val <= 0xFF_FF_FF_FF {
|
||||||
|
!val & 0x00_00_FF_FF_FF_FF
|
||||||
|
} else {
|
||||||
|
!val & 0x7F_FF_FF_FF_FF_FF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This case shouldn't happen here, as it's handled before
|
||||||
|
Invalid => 0,
|
||||||
|
};
|
||||||
|
Value::Int { val: out_val, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => match other {
|
||||||
|
// Propagate errors inside the value
|
||||||
|
Value::Error { .. } => other,
|
||||||
|
_ => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"numeric".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
105
crates/nu-command/src/bits/or.rs
Normal file
105
crates/nu-command/src/bits/or.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits or"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits or")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit or",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise or for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic or"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits or to two numbers",
|
||||||
|
example: "2 | bits or 6",
|
||||||
|
result: Some(Value::test_int(6)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical or to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits or 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val | target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
162
crates/nu-command/src/bits/rotate_left.rs
Normal file
162
crates/nu-command/src/bits/rotate_left.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits rol"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits rol")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a number with 2 bits",
|
||||||
|
example: "17 | bits rol 2",
|
||||||
|
result: Some(Value::test_int(68)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a list of numbers with 2 bits",
|
||||||
|
example: "[5 3 2] | bits rol 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_left(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_left(val as u8, bits, span),
|
||||||
|
Two => get_rotate_left(val as u16, bits, span),
|
||||||
|
Four => get_rotate_left(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
166
crates/nu-command/src/bits/rotate_right.rs
Normal file
166
crates/nu-command/src/bits/rotate_right.rs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits ror"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits ror")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a number with 60 bits",
|
||||||
|
example: "17 | bits ror 60",
|
||||||
|
result: Some(Value::test_int(272)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a list of numbers of one byte",
|
||||||
|
example: "[15 33 92] | bits ror 2 -n 1",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(195),
|
||||||
|
Value::test_int(72),
|
||||||
|
Value::test_int(23),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_right(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_right(val as u8, bits, span),
|
||||||
|
Two => get_rotate_right(val as u16, bits, span),
|
||||||
|
Four => get_rotate_right(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
188
crates/nu-command/src/bits/shift_left.rs
Normal file
188
crates/nu-command/src/bits/shift_left.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShl;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shl"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shl")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number by 7 bits",
|
||||||
|
example: "2 | bits shl 7",
|
||||||
|
result: Some(Value::test_int(256)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number with 1 byte by 7 bits",
|
||||||
|
example: "2 | bits shl 7 -n 1",
|
||||||
|
result: Some(Value::test_int(0)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a signed number by 1 bit",
|
||||||
|
example: "0x7F | bits shl 1 -s",
|
||||||
|
result: Some(Value::test_int(254)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a list of numbers",
|
||||||
|
example: "[5 3 2] | bits shl 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shl(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift left {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_left(val as u8, bits, span),
|
||||||
|
Two => get_shift_left(val as u16, bits, span),
|
||||||
|
Four => get_shift_left(val as u32, bits, span),
|
||||||
|
Eight => get_shift_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
178
crates/nu-command/src/bits/shift_right.rs
Normal file
178
crates/nu-command/src/bits/shift_right.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShr;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shr"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shr")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift right a number with 2 bits",
|
||||||
|
example: "8 | bits shr 2",
|
||||||
|
result: Some(Value::test_int(2)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift right a list of numbers",
|
||||||
|
example: "[15 35 2] | bits shr 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(3), Value::test_int(8), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shr(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift right {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_right(val as u8, bits, span),
|
||||||
|
Two => get_shift_right(val as u16, bits, span),
|
||||||
|
Four => get_shift_right(val as u32, bits, span),
|
||||||
|
Eight => get_shift_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
104
crates/nu-command/src/bits/xor.rs
Normal file
104
crates/nu-command/src/bits/xor.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits xor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits xor")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit xor",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise xor for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic xor"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits xor to two numbers",
|
||||||
|
example: "2 | bits xor 2",
|
||||||
|
result: Some(Value::test_int(0)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical xor to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits xor 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val ^ target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
185
crates/nu-command/src/bytes/add.rs
Normal file
185
crates/nu-command/src/bytes/add.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
added_data: Vec<u8>,
|
||||||
|
index: Option<usize>,
|
||||||
|
end: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesAdd;
|
||||||
|
|
||||||
|
impl Command for BytesAdd {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes add"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes add")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("data", SyntaxShape::Binary, "the binary to add")
|
||||||
|
.named(
|
||||||
|
"index",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"index to insert binary data",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch("end", "add to the end of binary", Some('e'))
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, add bytes to the data at the given cell paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Add specified bytes to the input"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["append", "truncate", "padding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
||||||
|
let end = call.has_flag("end");
|
||||||
|
|
||||||
|
let arg = Arguments {
|
||||||
|
added_data,
|
||||||
|
index,
|
||||||
|
end,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
|
||||||
|
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
|
||||||
|
example: "0x[FF AA AA] | bytes add 0x[11] -e",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0xAA, 0x11],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
|
||||||
|
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => add_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_impl(input: &[u8], args: &Arguments, span: Span) -> Value {
|
||||||
|
match args.index {
|
||||||
|
None => {
|
||||||
|
if args.end {
|
||||||
|
let mut added_data = args.added_data.clone();
|
||||||
|
let mut result = input.to_vec();
|
||||||
|
result.append(&mut added_data);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
} else {
|
||||||
|
let mut result = args.added_data.clone();
|
||||||
|
let mut input = input.to_vec();
|
||||||
|
result.append(&mut input);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(mut indx) => {
|
||||||
|
let inserted_index = if args.end {
|
||||||
|
input.len().saturating_sub(indx)
|
||||||
|
} else {
|
||||||
|
if indx > input.len() {
|
||||||
|
indx = input.len()
|
||||||
|
}
|
||||||
|
indx
|
||||||
|
};
|
||||||
|
let mut result = vec![];
|
||||||
|
let mut prev_data = input[..inserted_index].to_vec();
|
||||||
|
result.append(&mut prev_data);
|
||||||
|
let mut added_data = args.added_data.clone();
|
||||||
|
result.append(&mut added_data);
|
||||||
|
let mut after_data = input[inserted_index..].to_vec();
|
||||||
|
result.append(&mut after_data);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesAdd {})
|
||||||
|
}
|
||||||
|
}
|
319
crates/nu-command/src/bytes/at.rs
Normal file
319
crates/nu-command/src/bytes/at.rs
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesAt;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
start: isize,
|
||||||
|
end: isize,
|
||||||
|
arg_span: Span,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
|
||||||
|
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
|
||||||
|
let (start, end, span) = match range {
|
||||||
|
Value::List { mut vals, span } => {
|
||||||
|
if vals.len() != 2 {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"More than two indices in range".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let end = vals.pop().expect("Already check has size 2");
|
||||||
|
let end = match end {
|
||||||
|
Value::Int { val, .. } => val.to_string(),
|
||||||
|
Value::String { val, .. } => val,
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only string or list<int> ranges are supported".into(),
|
||||||
|
format!("input type: {:?}", other.get_type()),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let start = vals.pop().expect("Already check has size 1");
|
||||||
|
let start = match start {
|
||||||
|
Value::Int { val, .. } => val.to_string(),
|
||||||
|
Value::String { val, .. } => val,
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only string or list<int> ranges are supported".into(),
|
||||||
|
format!("input type: {:?}", other.get_type()),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(start, end, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String { val, span } => {
|
||||||
|
let splitted_result = val.split_once(',');
|
||||||
|
match splitted_result {
|
||||||
|
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
||||||
|
None => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start: isize = if start.is_empty() || start == "_" {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
match start.trim().parse() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let end: isize = if end.is_empty() || end == "_" {
|
||||||
|
isize::max_value()
|
||||||
|
} else {
|
||||||
|
match end.trim().parse() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((start, end, span))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for BytesAt {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes at"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes at")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, get bytes from data at the given cell paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["slice"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let range: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
let (start, end, arg_span) = parse_range(range, call.head)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
arg_span,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Alternatively, you can use the form",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Drop the last `n` characters from the string",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x33, 0x44, 0x55],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the remaining characters from a starting index",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10, 0x01, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the characters from the beginning until ending index",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x33, 0x44, 0x55, 0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Or the characters from the beginning until ending index inside a table",
|
||||||
|
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x11, 0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => at_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let len: isize = input.len() as isize;
|
||||||
|
|
||||||
|
let start: isize = if arg.start < 0 {
|
||||||
|
arg.start + len
|
||||||
|
} else {
|
||||||
|
arg.start
|
||||||
|
};
|
||||||
|
let end: isize = if arg.end < 0 {
|
||||||
|
std::cmp::max(len + arg.end, 0)
|
||||||
|
} else {
|
||||||
|
arg.end
|
||||||
|
};
|
||||||
|
|
||||||
|
if start < len && end >= 0 {
|
||||||
|
match start.cmp(&end) {
|
||||||
|
Ordering::Equal => Value::Binary { val: vec![], span },
|
||||||
|
Ordering::Greater => Value::Error {
|
||||||
|
error: ShellError::TypeMismatch(
|
||||||
|
"End must be greater than or equal to Start".to_string(),
|
||||||
|
arg.arg_span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Ordering::Less => Value::Binary {
|
||||||
|
val: {
|
||||||
|
let input_iter = input.iter().copied().skip(start as usize);
|
||||||
|
if end == isize::max_value() {
|
||||||
|
input_iter.collect()
|
||||||
|
} else {
|
||||||
|
input_iter.take((end - start) as usize).collect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Binary { val: vec![], span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesAt {})
|
||||||
|
}
|
||||||
|
}
|
84
crates/nu-command/src/bytes/build_.rs
Normal file
84
crates/nu-command/src/bytes/build_.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use nu_engine::eval_expression;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesBuild;
|
||||||
|
|
||||||
|
impl Command for BytesBuild {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes build"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create bytes from the arguments."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["concatenate", "join"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("bytes build")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Binary)])
|
||||||
|
.rest("rest", SyntaxShape::Any, "list of bytes")
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
|
||||||
|
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let mut output = vec![];
|
||||||
|
for expr in call.positional_iter() {
|
||||||
|
let val = eval_expression(engine_state, stack, expr)?;
|
||||||
|
match val {
|
||||||
|
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::TypeMismatch(
|
||||||
|
"only binary data arguments are supported".to_string(),
|
||||||
|
other.expect_span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesBuild {})
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_engine::get_full_help;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Bytes;
|
||||||
|
|
||||||
|
impl Command for Bytes {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes")
|
||||||
|
.category(Category::Bytes)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Various commands for working with byte data"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: get_full_help(
|
||||||
|
&Bytes.signature(),
|
||||||
|
&Bytes.examples(),
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
130
crates/nu-command/src/bytes/collect.rs
Normal file
130
crates/nu-command/src/bytes/collect.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct BytesCollect;
|
||||||
|
|
||||||
|
impl Command for BytesCollect {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes collect"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes collect")
|
||||||
|
.input_output_types(vec![(Type::List(Box::new(Type::Binary)), Type::Binary)])
|
||||||
|
.optional(
|
||||||
|
"separator",
|
||||||
|
SyntaxShape::Binary,
|
||||||
|
"optional separator to use when creating binary",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Concatenate multiple binary into a single binary, with an optional separator between each"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["join", "concatenate"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
|
||||||
|
// input should be a list of binary data.
|
||||||
|
let mut output_binary = vec![];
|
||||||
|
for value in input {
|
||||||
|
match value {
|
||||||
|
Value::Binary { mut val, .. } => {
|
||||||
|
output_binary.append(&mut val);
|
||||||
|
// manually concat
|
||||||
|
// TODO: make use of std::slice::Join when it's available in stable.
|
||||||
|
if let Some(sep) = &separator {
|
||||||
|
let mut work_sep = sep.clone();
|
||||||
|
output_binary.append(&mut work_sep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
call.head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match separator {
|
||||||
|
None => Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data()),
|
||||||
|
Some(sep) => {
|
||||||
|
if output_binary.is_empty() {
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
// have push one extra separator in previous step, pop them out.
|
||||||
|
for _ in sep {
|
||||||
|
let _ = output_binary.pop();
|
||||||
|
}
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Create a byte array from input",
|
||||||
|
example: "[0x[11] 0x[13 15]] | bytes collect",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x11, 0x13, 0x15],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Create a byte array from input with a separator",
|
||||||
|
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesCollect {})
|
||||||
|
}
|
||||||
|
}
|
117
crates/nu-command/src/bytes/ends_with.rs
Normal file
117
crates/nu-command/src/bytes/ends_with.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesEndsWith;
|
||||||
|
|
||||||
|
impl Command for BytesEndsWith {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes ends-with"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes ends-with")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Bool)])
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, check if bytes at the given cell paths end with the pattern",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check if bytes ends with a pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
||||||
|
result: Some(Value::test_bool(true)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
||||||
|
result: Some(Value::test_bool(true)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[11]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||||
|
result: Some(Value::test_bool(false)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::boolean(val.ends_with(&args.pattern), *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesEndsWith {})
|
||||||
|
}
|
||||||
|
}
|
230
crates/nu-command/src/bytes/index_of.rs
Normal file
230
crates/nu-command/src/bytes/index_of.rs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
end: bool,
|
||||||
|
all: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesIndexOf;
|
||||||
|
|
||||||
|
impl Command for BytesIndexOf {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes index-of"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes index-of")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::Int),
|
||||||
|
(Type::Binary, Type::List(Box::new(Type::Int))),
|
||||||
|
])
|
||||||
|
.required(
|
||||||
|
"pattern",
|
||||||
|
SyntaxShape::Binary,
|
||||||
|
"the pattern to find index of",
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, find the indexes at the given cell paths",
|
||||||
|
)
|
||||||
|
.switch("all", "returns all matched index", Some('a'))
|
||||||
|
.switch("end", "search from the end of the binary", Some('e'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
end: call.has_flag("end"),
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern in bytes",
|
||||||
|
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
|
||||||
|
result: Some(Value::test_int(1)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern, search from end",
|
||||||
|
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
|
||||||
|
result: Some(Value::test_int(6)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns all matched index",
|
||||||
|
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns all matched index, searching from end",
|
||||||
|
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern for specific column",
|
||||||
|
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(0),
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::test_int(-1),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => index_of_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
if arg.all {
|
||||||
|
search_all_index(input, &arg.pattern, arg.end, span)
|
||||||
|
} else {
|
||||||
|
let mut iter = input.windows(arg.pattern.len());
|
||||||
|
|
||||||
|
if arg.end {
|
||||||
|
Value::Int {
|
||||||
|
val: iter
|
||||||
|
.rev()
|
||||||
|
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||||
|
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
|
||||||
|
.unwrap_or(-1),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Int {
|
||||||
|
val: iter
|
||||||
|
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||||
|
.map(|x| x as i64)
|
||||||
|
.unwrap_or(-1),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
|
||||||
|
let mut result = vec![];
|
||||||
|
if from_end {
|
||||||
|
let (mut left, mut right) = (
|
||||||
|
input.len() as isize - pattern.len() as isize,
|
||||||
|
input.len() as isize,
|
||||||
|
);
|
||||||
|
while left >= 0 {
|
||||||
|
if &input[left as usize..right as usize] == pattern {
|
||||||
|
result.push(Value::Int {
|
||||||
|
val: left as i64,
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
left -= pattern.len() as isize;
|
||||||
|
right -= pattern.len() as isize;
|
||||||
|
} else {
|
||||||
|
left -= 1;
|
||||||
|
right -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::List { vals: result, span }
|
||||||
|
} else {
|
||||||
|
// doing find stuff.
|
||||||
|
let (mut left, mut right) = (0, pattern.len());
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = pattern.len();
|
||||||
|
while right <= input_len {
|
||||||
|
if &input[left..right] == pattern {
|
||||||
|
result.push(Value::Int {
|
||||||
|
val: left as i64,
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
} else {
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::List { vals: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesIndexOf {})
|
||||||
|
}
|
||||||
|
}
|
98
crates/nu-command/src/bytes/length.rs
Normal file
98
crates/nu-command/src/bytes/length.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesLen;
|
||||||
|
|
||||||
|
impl Command for BytesLen {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes length"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes length")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, find the length of data at the given cell paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Output the length of any bytes in the pipeline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["size", "count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Return the lengths of multiple strings",
|
||||||
|
example: "0x[1F FF AA AB] | bytes length",
|
||||||
|
result: Some(Value::test_int(4)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Return the lengths of multiple strings",
|
||||||
|
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(4), Value::test_int(1)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::int(val.len() as i64, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesLen {})
|
||||||
|
}
|
||||||
|
}
|
25
crates/nu-command/src/bytes/mod.rs
Normal file
25
crates/nu-command/src/bytes/mod.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
mod add;
|
||||||
|
mod at;
|
||||||
|
mod build_;
|
||||||
|
mod bytes_;
|
||||||
|
mod collect;
|
||||||
|
mod ends_with;
|
||||||
|
mod index_of;
|
||||||
|
mod length;
|
||||||
|
mod remove;
|
||||||
|
mod replace;
|
||||||
|
mod reverse;
|
||||||
|
mod starts_with;
|
||||||
|
|
||||||
|
pub use add::BytesAdd;
|
||||||
|
pub use at::BytesAt;
|
||||||
|
pub use build_::BytesBuild;
|
||||||
|
pub use bytes_::Bytes;
|
||||||
|
pub use collect::BytesCollect;
|
||||||
|
pub use ends_with::BytesEndsWith;
|
||||||
|
pub use index_of::BytesIndexOf;
|
||||||
|
pub use length::BytesLen;
|
||||||
|
pub use remove::BytesRemove;
|
||||||
|
pub use replace::BytesReplace;
|
||||||
|
pub use reverse::BytesReverse;
|
||||||
|
pub use starts_with::BytesStartsWith;
|
214
crates/nu-command/src/bytes/remove.rs
Normal file
214
crates/nu-command/src/bytes/remove.rs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
end: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesRemove;
|
||||||
|
|
||||||
|
impl Command for BytesRemove {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes remove"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes remove")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, remove bytes from data at the given cell paths",
|
||||||
|
)
|
||||||
|
.switch("end", "remove from end of binary", Some('e'))
|
||||||
|
.switch("all", "remove occurrences of finding binary", Some('a'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Remove bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["search", "shift", "switch"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
|
if pattern_to_remove.item.is_empty() {
|
||||||
|
return Err(ShellError::TypeMismatch(
|
||||||
|
"the pattern to remove cannot be empty".to_string(),
|
||||||
|
pattern_to_remove.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern: pattern_to_remove,
|
||||||
|
end: call.has_flag("end"),
|
||||||
|
cell_paths,
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Remove contents",
|
||||||
|
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove all occurrences of find binary",
|
||||||
|
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xBB],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove occurrences of find binary from end",
|
||||||
|
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove all occurrences of find binary in table",
|
||||||
|
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x17, 0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => remove_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let mut result = vec![];
|
||||||
|
let remove_all = arg.all;
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = arg.pattern.len();
|
||||||
|
|
||||||
|
// Note:
|
||||||
|
// remove_all from start and end will generate the same result.
|
||||||
|
// so we'll put `remove_all` relative logic into else clouse.
|
||||||
|
if arg.end && !remove_all {
|
||||||
|
let (mut left, mut right) = (
|
||||||
|
input.len() as isize - arg.pattern.len() as isize,
|
||||||
|
input.len() as isize,
|
||||||
|
);
|
||||||
|
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
|
||||||
|
result.push(input[right as usize - 1]);
|
||||||
|
left -= 1;
|
||||||
|
right -= 1;
|
||||||
|
}
|
||||||
|
// append the remaining thing to result, this can be happeneed when
|
||||||
|
// we have something to remove and remove_all is False.
|
||||||
|
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||||
|
result.append(&mut remain);
|
||||||
|
result = result.into_iter().rev().collect();
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
} else {
|
||||||
|
let (mut left, mut right) = (0, arg.pattern.len());
|
||||||
|
while right <= input_len {
|
||||||
|
if input[left..right] == arg.pattern {
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
if !remove_all {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(input[left]);
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append the remaining thing to result, this can happened when
|
||||||
|
// we have something to remove and remove_all is False.
|
||||||
|
let mut remain = input[left..].to_vec();
|
||||||
|
result.append(&mut remain);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesRemove {})
|
||||||
|
}
|
||||||
|
}
|
189
crates/nu-command/src/bytes/replace.rs
Normal file
189
crates/nu-command/src/bytes/replace.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
find: Vec<u8>,
|
||||||
|
replace: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesReplace;
|
||||||
|
|
||||||
|
impl Command for BytesReplace {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes replace"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes replace")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||||
|
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, replace bytes in data at the given cell paths",
|
||||||
|
)
|
||||||
|
.switch("all", "replace all occurrences of find binary", Some('a'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Find and replace binary"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["search", "shift", "switch"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
|
if find.item.is_empty() {
|
||||||
|
return Err(ShellError::TypeMismatch(
|
||||||
|
"the pattern to find cannot be empty".to_string(),
|
||||||
|
find.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = Arguments {
|
||||||
|
find: find.item,
|
||||||
|
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
||||||
|
cell_paths,
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Find and replace contents",
|
||||||
|
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Find and replace all occurrences of find binary",
|
||||||
|
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Find and replace all occurrences of find binary in table",
|
||||||
|
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x13, 0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x17, 0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => replace_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let mut replaced = vec![];
|
||||||
|
let replace_all = arg.all;
|
||||||
|
|
||||||
|
// doing find-and-replace stuff.
|
||||||
|
let (mut left, mut right) = (0, arg.find.len());
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = arg.find.len();
|
||||||
|
while right <= input_len {
|
||||||
|
if input[left..right] == arg.find {
|
||||||
|
let mut to_replace = arg.replace.clone();
|
||||||
|
replaced.append(&mut to_replace);
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
if !replace_all {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replaced.push(input[left]);
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut remain = input[left..].to_vec();
|
||||||
|
replaced.append(&mut remain);
|
||||||
|
Value::Binary {
|
||||||
|
val: replaced,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesReplace {})
|
||||||
|
}
|
||||||
|
}
|
108
crates/nu-command/src/bytes/reverse.rs
Normal file
108
crates/nu-command/src/bytes/reverse.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesReverse;
|
||||||
|
|
||||||
|
impl Command for BytesReverse {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes reverse"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes reverse")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, reverse data at the given cell paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Reverse the bytes in the pipeline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["convert", "inverse", "flip"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Reverse bytes `0x[1F FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes reverse",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Reverse bytes `0x[FF AA AA]`",
|
||||||
|
example: "0x[FF AA AA] | bytes reverse",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => {
|
||||||
|
let mut reversed_input = val.to_vec();
|
||||||
|
reversed_input.reverse();
|
||||||
|
Value::Binary {
|
||||||
|
val: reversed_input,
|
||||||
|
span: *val_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesReverse {})
|
||||||
|
}
|
||||||
|
}
|
123
crates/nu-command/src/bytes/starts_with.rs
Normal file
123
crates/nu-command/src/bytes/starts_with.rs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesStartsWith;
|
||||||
|
|
||||||
|
impl Command for BytesStartsWith {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes starts-with"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes starts-with")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Bool)])
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, check if bytes at the given cell paths start with the pattern",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check if bytes starts with a pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(
|
||||||
|
starts_with,
|
||||||
|
arg,
|
||||||
|
input,
|
||||||
|
call.head,
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F FF AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
||||||
|
result: Some(Value::test_bool(true)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||||
|
result: Some(Value::test_bool(true)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||||
|
result: Some(Value::test_bool(false)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::boolean(val.starts_with(&args.pattern), *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"binary".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesStartsWith {})
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher};
|
|||||||
/// ```text
|
/// ```text
|
||||||
/// assert_eq!(HashableValue::Bool {val: true, span: Span{start: 0, end: 1}}, HashableValue::Bool {val: true, span: Span{start: 90, end: 1000}})
|
/// assert_eq!(HashableValue::Bool {val: true, span: Span{start: 0, end: 1}}, HashableValue::Bool {val: true, span: Span{start: 90, end: 1000}})
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Eq, Debug)]
|
#[derive(Eq, Debug, Ord, PartialOrd)]
|
||||||
pub enum HashableValue {
|
pub enum HashableValue {
|
||||||
Bool {
|
Bool {
|
||||||
val: bool,
|
val: bool,
|
||||||
@ -53,7 +53,7 @@ impl Default for HashableValue {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: false,
|
val: false,
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,13 +78,14 @@ impl HashableValue {
|
|||||||
Value::String { val, span } => Ok(HashableValue::String { val, span }),
|
Value::String { val, span } => Ok(HashableValue::String { val, span }),
|
||||||
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
|
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
|
||||||
|
|
||||||
_ => {
|
// Explicitly propagate errors instead of dropping them.
|
||||||
let input_span = value.span().unwrap_or(span);
|
Value::Error { error } => Err(error),
|
||||||
Err(ShellError::UnsupportedInput(
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
format!("input value {value:?} is not hashable"),
|
"input value is not hashable".into(),
|
||||||
input_span,
|
format!("input type: {:?}", value.get_type()),
|
||||||
))
|
span,
|
||||||
}
|
value.expect_span(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +215,7 @@ mod test {
|
|||||||
];
|
];
|
||||||
for (val, expect_hashable_val) in values.into_iter() {
|
for (val, expect_hashable_val) in values.into_iter() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::from_value(val, Span { start: 0, end: 0 }).unwrap(),
|
HashableValue::from_value(val, Span::unknown()).unwrap(),
|
||||||
expect_hashable_val
|
expect_hashable_val
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -228,7 +229,7 @@ mod test {
|
|||||||
vals: vec![Value::Bool { val: true, span }],
|
vals: vec![Value::Bool { val: true, span }],
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val: 0,
|
val: 0,
|
||||||
captures: HashMap::new(),
|
captures: HashMap::new(),
|
||||||
span,
|
span,
|
||||||
@ -245,7 +246,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
for v in values {
|
for v in values {
|
||||||
assert!(HashableValue::from_value(v, Span { start: 0, end: 0 }).is_err())
|
assert!(HashableValue::from_value(v, Span::unknown()).is_err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +267,7 @@ mod test {
|
|||||||
for val in values.into_iter() {
|
for val in values.into_iter() {
|
||||||
let expected_val = val.clone();
|
let expected_val = val.clone();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::from_value(val, Span { start: 0, end: 0 })
|
HashableValue::from_value(val, Span::unknown())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_value(),
|
.into_value(),
|
||||||
expected_val
|
expected_val
|
||||||
@ -279,14 +280,11 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: Span { start: 0, end: 1 }
|
span: Span::new(0, 1)
|
||||||
},
|
},
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: Span {
|
span: Span::new(90, 1000)
|
||||||
start: 90,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -299,7 +297,7 @@ mod test {
|
|||||||
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
||||||
|
|
||||||
// hashable value doesn't care about span.
|
// hashable value doesn't care about span.
|
||||||
let diff_span = Span { start: 1, end: 2 };
|
let diff_span = Span::new(1, 2);
|
||||||
set.insert(HashableValue::Bool {
|
set.insert(HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: diff_span,
|
span: diff_span,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use super::hashable_value::HashableValue;
|
use super::hashable_value::HashableValue;
|
||||||
|
use itertools::Itertools;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
|
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
@ -24,6 +25,7 @@ impl Command for Histogram {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("histogram")
|
Signature::build("histogram")
|
||||||
|
.input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Table(vec![])),])
|
||||||
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
|
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
|
||||||
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
|
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
|
||||||
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
|
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
|
||||||
@ -36,24 +38,49 @@ impl Command for Histogram {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for the types of files",
|
description: "Compute a histogram of file types",
|
||||||
example: "ls | histogram type",
|
example: "ls | histogram type",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Get a histogram for the types of files, with frequency column named freq",
|
"Compute a histogram for the types of files, with frequency column named freq",
|
||||||
example: "ls | histogram type freq",
|
example: "ls | histogram type freq",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for a list of numbers",
|
description: "Compute a histogram for a list of numbers",
|
||||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
|
example: "[1 2 1] | histogram",
|
||||||
result: None,
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_float(0.6666666666666666),
|
||||||
|
Value::test_string("66.67%"),
|
||||||
|
Value::test_string("******************************************************************"),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_float(0.3333333333333333),
|
||||||
|
Value::test_string("33.33%"),
|
||||||
|
Value::test_string("*********************************"),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for a list of numbers, and percentage is based on the maximum value",
|
description: "Compute a histogram for a list of numbers, and percentage is based on the maximum value",
|
||||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
|
example: "[1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
|
||||||
result: None,
|
result: None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -71,12 +98,11 @@ impl Command for Histogram {
|
|||||||
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
|
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
|
||||||
let frequency_column_name = match frequency_name_arg {
|
let frequency_column_name = match frequency_name_arg {
|
||||||
Some(inner) => {
|
Some(inner) => {
|
||||||
let span = inner.span;
|
|
||||||
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
|
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"frequency-column-name can't be 'value', 'count' or 'percentage'"
|
"frequency-column-name can't be 'value', 'count' or 'percentage'"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
span,
|
inner.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
inner.item
|
inner.item
|
||||||
@ -92,7 +118,7 @@ impl Command for Histogram {
|
|||||||
"normalize" => PercentageCalcMethod::Normalize,
|
"normalize" => PercentageCalcMethod::Normalize,
|
||||||
"relative" => PercentageCalcMethod::Relative,
|
"relative" => PercentageCalcMethod::Relative,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"calc method can only be 'normalize' or 'relative'".to_string(),
|
"calc method can only be 'normalize' or 'relative'".to_string(),
|
||||||
inner.span,
|
inner.span,
|
||||||
))
|
))
|
||||||
@ -110,6 +136,8 @@ impl Command for Histogram {
|
|||||||
frequency_column_name,
|
frequency_column_name,
|
||||||
calc_method,
|
calc_method,
|
||||||
span,
|
span,
|
||||||
|
// Note that as_list() filters out Value::Error here.
|
||||||
|
data_as_value.expect_span(),
|
||||||
),
|
),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
@ -122,6 +150,7 @@ fn run_histogram(
|
|||||||
freq_column: String,
|
freq_column: String,
|
||||||
calc_method: PercentageCalcMethod,
|
calc_method: PercentageCalcMethod,
|
||||||
head_span: Span,
|
head_span: Span,
|
||||||
|
list_span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let mut inputs = vec![];
|
let mut inputs = vec![];
|
||||||
// convert from inputs to hashable values.
|
// convert from inputs to hashable values.
|
||||||
@ -130,14 +159,24 @@ fn run_histogram(
|
|||||||
// some invalid input scenario needs to handle:
|
// some invalid input scenario needs to handle:
|
||||||
// Expect input is a list of hashable value, if one value is not hashable, throw out error.
|
// Expect input is a list of hashable value, if one value is not hashable, throw out error.
|
||||||
for v in values {
|
for v in values {
|
||||||
let current_span = v.span().unwrap_or(head_span);
|
match v {
|
||||||
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
|
// Propagate existing errors.
|
||||||
ShellError::UnsupportedInput(
|
Value::Error { error } => return Err(error),
|
||||||
"--column-name is not provided, can only support a list of simple value."
|
_ => {
|
||||||
.to_string(),
|
let t = v.get_type();
|
||||||
current_span,
|
let span = v.expect_span();
|
||||||
)
|
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
|
||||||
})?);
|
ShellError::UnsupportedInput(
|
||||||
|
"Since --column-name was not provided, only lists of hashable values are supported.".to_string(),
|
||||||
|
format!(
|
||||||
|
"input type: {:?}", t
|
||||||
|
),
|
||||||
|
head_span,
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ref col) => {
|
Some(ref col) => {
|
||||||
@ -159,14 +198,17 @@ fn run_histogram(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate existing errors.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::CantFindColumn(
|
||||||
format!("expect input is table, and inputs doesn't contain any value which has {col_name} column"),
|
col_name.clone(),
|
||||||
head_span,
|
head_span,
|
||||||
|
list_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,34 +255,42 @@ fn histogram_impl(
|
|||||||
freq_column.to_string(),
|
freq_column.to_string(),
|
||||||
];
|
];
|
||||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter() {
|
for (val, count) in counter.into_iter().sorted() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||||
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
|
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
|
||||||
|
|
||||||
result.push(Value::Record {
|
result.push((
|
||||||
cols: result_cols.clone(),
|
count, // attach count first for easily sorting.
|
||||||
vals: vec![
|
Value::Record {
|
||||||
val.into_value(),
|
cols: result_cols.clone(),
|
||||||
Value::Int { val: count, span },
|
vals: vec![
|
||||||
Value::Float {
|
val.into_value(),
|
||||||
val: quantile,
|
Value::Int { val: count, span },
|
||||||
span,
|
Value::Float {
|
||||||
},
|
val: quantile,
|
||||||
Value::String {
|
span,
|
||||||
val: percentage,
|
},
|
||||||
span,
|
Value::String {
|
||||||
},
|
val: percentage,
|
||||||
Value::String { val: freq, span },
|
span,
|
||||||
],
|
},
|
||||||
span,
|
Value::String { val: freq, span },
|
||||||
});
|
],
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Value::List { vals: result, span }.into_pipeline_data()
|
result.sort_by(|a, b| b.0.cmp(&a.0));
|
||||||
|
Value::List {
|
||||||
|
vals: result.into_iter().map(|x| x.1).collect(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -18,7 +19,9 @@ impl Command for Fmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("fmt").category(Category::Conversions)
|
Signature::build("fmt")
|
||||||
|
.input_output_types(vec![(Type::Number, Type::Record(vec![]))])
|
||||||
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -41,38 +44,14 @@ impl Command for Fmt {
|
|||||||
"upperhex".into(),
|
"upperhex".into(),
|
||||||
],
|
],
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::String {
|
Value::test_string("0b101010"),
|
||||||
val: "0b101010".to_string(),
|
Value::test_string("42"),
|
||||||
span: Span::test_data(),
|
Value::test_string("42"),
|
||||||
},
|
Value::test_string("4.2e1"),
|
||||||
Value::String {
|
Value::test_string("0x2a"),
|
||||||
val: "42".to_string(),
|
Value::test_string("0o52"),
|
||||||
span: Span::test_data(),
|
Value::test_string("4.2E1"),
|
||||||
},
|
Value::test_string("0x2A"),
|
||||||
Value::String {
|
|
||||||
val: "42".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "4.2e1".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0x2a".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0o52".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "4.2E1".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0x2A".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
}),
|
}),
|
||||||
@ -96,38 +75,24 @@ fn fmt(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => fmt_it(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
Value::Filesize { val, .. } => fmt_it(*val, span),
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
format!("unsupported input type: {:?}", input.get_type()),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer or filesize".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,10 +17,20 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into binary")
|
Signature::build("into binary")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
|
(Type::Int, Type::Binary),
|
||||||
|
(Type::Number, Type::Binary),
|
||||||
|
(Type::String, Type::Binary),
|
||||||
|
(Type::Bool, Type::Binary),
|
||||||
|
(Type::Filesize, Type::Binary),
|
||||||
|
(Type::Date, Type::Binary),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to binary (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -29,7 +40,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "binary", "bytes", "bin"]
|
vec!["convert", "bytes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -100,7 +111,7 @@ fn into_binary(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
||||||
@ -120,27 +131,10 @@ fn into_binary(
|
|||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
_ => input.map(
|
_ => {
|
||||||
move |v| {
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
if column_paths.is_empty() {
|
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
action(&v, head)
|
}
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +154,7 @@ fn float_to_endian(n: f64) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { .. } => input.clone(),
|
Value::Binary { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Binary {
|
Value::Int { val, .. } => Value::Binary {
|
||||||
@ -180,16 +174,27 @@ pub fn action(input: &Value, span: Span) -> Value {
|
|||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Bool { val, .. } => Value::Binary {
|
Value::Bool { val, .. } => Value::Binary {
|
||||||
val: int_to_endian(if *val { 1i64 } else { 0 }),
|
val: int_to_endian(i64::from(*val)),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Duration { val, .. } => Value::Binary {
|
||||||
|
val: int_to_endian(*val),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Date { val, .. } => Value::Binary {
|
Value::Date { val, .. } => Value::Binary {
|
||||||
val: val.format("%c").to_string().as_bytes().to_vec(),
|
val: val.format("%c").to_string().as_bytes().to_vec(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
_ => Value::Error {
|
Value::Error { .. } => input.clone(),
|
||||||
error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer, float, filesize, string, date, duration, binary or bool".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,10 +16,17 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into bool")
|
Signature::build("into bool")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::Bool),
|
||||||
|
(Type::Number, Type::Bool),
|
||||||
|
(Type::String, Type::Bool),
|
||||||
|
(Type::Bool, Type::Bool),
|
||||||
|
(Type::List(Box::new(Type::Any)), Type::Table(vec![])),
|
||||||
|
])
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to boolean (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -46,7 +54,7 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert value to boolean in table",
|
description: "Convert value to boolean in table",
|
||||||
example: "echo [[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
|
example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::Record {
|
Value::Record {
|
||||||
@ -88,6 +96,11 @@ impl Command for SubCommand {
|
|||||||
example: "1 | into bool",
|
example: "1 | into bool",
|
||||||
result: Some(Value::boolean(true, span)),
|
result: Some(Value::boolean(true, span)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert decimal to boolean",
|
||||||
|
example: "0.3 | into bool",
|
||||||
|
result: Some(Value::boolean(true, span)),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal string to boolean",
|
description: "convert decimal string to boolean",
|
||||||
example: "'0.0' | into bool",
|
example: "'0.0' | into bool",
|
||||||
@ -108,28 +121,9 @@ fn into_bool(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
||||||
@ -154,7 +148,7 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Bool { .. } => input.clone(),
|
Value::Bool { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Bool {
|
Value::Int { val, .. } => Value::Bool {
|
||||||
@ -169,10 +163,15 @@ fn action(input: &Value, span: Span) -> Value {
|
|||||||
Ok(val) => Value::Bool { val, span },
|
Ok(val) => Value::Bool { val, span },
|
||||||
Err(error) => Value::Error { error },
|
Err(error) => Value::Error { error },
|
||||||
},
|
},
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
"'into bool' does not support this input".into(),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"bool, integer, float or string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,13 +14,19 @@ impl Command for Into {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into").category(Category::Conversions)
|
Signature::build("into")
|
||||||
|
.category(Category::Conversions)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Commands to convert data from one type to another."
|
"Commands to convert data from one type to another."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -29,21 +35,15 @@ impl Command for Into {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(&Into.signature(), &[], engine_state, stack),
|
val: get_full_help(
|
||||||
|
&Into.signature(),
|
||||||
|
&[],
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
span: call.head,
|
span: call.head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Into {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone, Utc};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generate_strftime_list;
|
|
||||||
use crate::parse_date_from_string;
|
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
timezone: Option<Spanned<String>>,
|
zone_options: Option<Spanned<Zone>>,
|
||||||
offset: Option<Spanned<i64>>,
|
format_options: Option<DatetimeFormat>,
|
||||||
format: Option<String>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
column_paths: Vec<CellPath>,
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case it may be confused with chrono::TimeZone
|
// In case it may be confused with chrono::TimeZone
|
||||||
@ -59,7 +64,11 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into datetime")
|
Signature::build("into datetime")
|
||||||
.named(
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::Date),
|
||||||
|
(Type::String, Type::Date),
|
||||||
|
])
|
||||||
|
.named(
|
||||||
"timezone",
|
"timezone",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
|
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
|
||||||
@ -85,7 +94,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally convert text into datetime by column paths",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -97,7 +106,36 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
if call.has_flag("list") {
|
||||||
|
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
// if zone-offset is specified, then zone will be neglected
|
||||||
|
let timezone = call.get_flag::<Spanned<String>>(engine_state, stack, "timezone")?;
|
||||||
|
let zone_options =
|
||||||
|
match &call.get_flag::<Spanned<i64>>(engine_state, stack, "offset")? {
|
||||||
|
Some(zone_offset) => Some(Spanned {
|
||||||
|
item: Zone::new(zone_offset.item),
|
||||||
|
span: zone_offset.span,
|
||||||
|
}),
|
||||||
|
None => timezone.as_ref().map(|zone| Spanned {
|
||||||
|
item: Zone::from_string(zone.item.clone()),
|
||||||
|
span: zone.span,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let format_options = call
|
||||||
|
.get_flag::<String>(engine_state, stack, "format")?
|
||||||
|
.as_ref()
|
||||||
|
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
format_options,
|
||||||
|
zone_options,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -105,42 +143,45 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "date", "time", "timezone", "UTC"]
|
vec!["convert", "timezone", "UTC"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
let example_result_1 = |secs: i64, nsecs: u32| match Utc.timestamp_opt(secs, nsecs) {
|
||||||
|
LocalResult::Single(dt) => Some(Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
_ => panic!("datetime: help example is invalid"),
|
||||||
|
};
|
||||||
|
let example_result_2 = |millis: i64| match Utc.timestamp_millis_opt(millis) {
|
||||||
|
LocalResult::Single(dt) => Some(Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
_ => panic!("datetime: help example is invalid"),
|
||||||
|
};
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434100,0)
|
||||||
val: Utc.timestamp(1614434100, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'2021-02-27T13:55:40+00:00' | into datetime",
|
example: "'2021-02-27T13:55:40+00:00' | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime using a custom format",
|
description: "Convert to datetime using a custom format",
|
||||||
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
|
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
|
||||||
example: "1614434140 | into datetime",
|
example: "1614434140 | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
@ -148,6 +189,12 @@ impl Command for SubCommand {
|
|||||||
example: "1614434140 | into datetime -o +9",
|
example: "1614434140 | into datetime -o +9",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Convert a millisecond-precise timestamp",
|
||||||
|
example: "1656165681720 | into datetime",
|
||||||
|
result: example_result_2(1656165681720)
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,81 +202,23 @@ impl Command for SubCommand {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DatetimeFormat(String);
|
struct DatetimeFormat(String);
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
let timezone = &args.zone_options;
|
||||||
stack: &mut Stack,
|
let dateformat = &args.format_options;
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let options = Arguments {
|
|
||||||
timezone: call.get_flag(engine_state, stack, "timezone")?,
|
|
||||||
offset: call.get_flag(engine_state, stack, "offset")?,
|
|
||||||
format: call.get_flag(engine_state, stack, "format")?,
|
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if zone-offset is specified, then zone will be neglected
|
|
||||||
let zone_options = match &options.offset {
|
|
||||||
Some(zone_offset) => Some(Spanned {
|
|
||||||
item: Zone::new(zone_offset.item),
|
|
||||||
span: zone_offset.span,
|
|
||||||
}),
|
|
||||||
None => options.timezone.as_ref().map(|zone| Spanned {
|
|
||||||
item: Zone::from_string(zone.item.clone()),
|
|
||||||
span: zone.span,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let list_flag = call.has_flag("list");
|
|
||||||
|
|
||||||
let format_options = options
|
|
||||||
.format
|
|
||||||
.as_ref()
|
|
||||||
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if options.column_paths.is_empty() && !list_flag {
|
|
||||||
action(&v, &zone_options, &format_options, head)
|
|
||||||
} else if list_flag {
|
|
||||||
generate_strftime_list(head, true)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &options.column_paths {
|
|
||||||
let zone_options = zone_options.clone();
|
|
||||||
let format_options = format_options.clone();
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, &zone_options, &format_options, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(
|
|
||||||
input: &Value,
|
|
||||||
timezone: &Option<Spanned<Zone>>,
|
|
||||||
dateformat: &Option<DatetimeFormat>,
|
|
||||||
head: Span,
|
|
||||||
) -> Value {
|
|
||||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
Value::Int { val, .. } => Ok(*val),
|
Value::Int { val, .. } => Ok(*val),
|
||||||
Value::String { val, .. } => val.parse::<i64>(),
|
Value::String { val, .. } => val.parse::<i64>(),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => return input.clone(),
|
||||||
other => {
|
other => {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!("Expected string or int, got {} instead", other.get_type()),
|
"string and integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
head,
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -242,45 +231,68 @@ fn action(
|
|||||||
if ts.abs() > TIMESTAMP_BOUND {
|
if ts.abs() > TIMESTAMP_BOUND {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"Given timestamp is out of range, it should between -8e+12 and 8e+12"
|
"timestamp is out of range; it should between -8e+12 and 8e+12".to_string(),
|
||||||
.to_string(),
|
format!("timestamp is {:?}", ts),
|
||||||
head,
|
head,
|
||||||
|
// Again, can safely unwrap this from here on
|
||||||
|
input.expect_span(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! match_datetime {
|
||||||
|
($expr:expr) => {
|
||||||
|
match $expr {
|
||||||
|
LocalResult::Single(dt) => Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: head,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"The given local datetime representation is invalid.".into(),
|
||||||
|
format!("timestamp is {:?}", ts),
|
||||||
|
head,
|
||||||
|
head,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return match timezone {
|
return match timezone {
|
||||||
// default to UTC
|
// default to UTC
|
||||||
None => Value::Date {
|
None => {
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
// be able to convert chrono::Utc::now()
|
||||||
span: head,
|
match ts.to_string().len() {
|
||||||
},
|
x if x > 13 => Value::Date {
|
||||||
|
val: Utc.timestamp_nanos(ts).into(),
|
||||||
|
span: head,
|
||||||
|
},
|
||||||
|
x if x > 10 => match_datetime!(Utc.timestamp_millis_opt(ts)),
|
||||||
|
_ => match_datetime!(Utc.timestamp_opt(ts, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(Spanned { item, span }) => match item {
|
Some(Spanned { item, span }) => match item {
|
||||||
Zone::Utc => Value::Date {
|
Zone::Utc => match_datetime!(Utc.timestamp_opt(ts, 0)),
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
Zone::Local => match_datetime!(Local.timestamp_opt(ts, 0)),
|
||||||
span: head,
|
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||||
|
Some(eastoffset) => match_datetime!(eastoffset.timestamp_opt(ts, 0)),
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(*span),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Zone::Local => Value::Date {
|
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||||
val: Local.timestamp(ts, 0).into(),
|
Some(westoffset) => match_datetime!(westoffset.timestamp_opt(ts, 0)),
|
||||||
span: head,
|
None => Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(*span),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Zone::East(i) => {
|
|
||||||
let eastoffset = FixedOffset::east((*i as i32) * HOUR);
|
|
||||||
Value::Date {
|
|
||||||
val: eastoffset.timestamp(ts, 0),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Zone::West(i) => {
|
|
||||||
let westoffset = FixedOffset::west((*i as i32) * HOUR);
|
|
||||||
Value::Date {
|
|
||||||
val: westoffset.timestamp(ts, 0),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Zone::Error => Value::Error {
|
Zone::Error => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
// This is an argument error, not an input error
|
||||||
"Cannot convert given timezone or offset to timestamp".to_string(),
|
error: ShellError::TypeMismatch(
|
||||||
|
"Invalid timezone or offset".to_string(),
|
||||||
*span,
|
*span,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -295,7 +307,7 @@ fn action(
|
|||||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||||
Ok(d) => Value::Date { val: d, span: head },
|
Ok(d) => Value::Date { val: d, span: head },
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
return Value::Error {
|
Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
format!("could not parse as datetime using format '{}'", dt.0),
|
format!("could not parse as datetime using format '{}'", dt.0),
|
||||||
reason.to_string(),
|
reason.to_string(),
|
||||||
@ -317,10 +329,15 @@ fn action(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!("Expected string, got {} instead", other.get_type()),
|
"string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
head,
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -343,7 +360,12 @@ mod tests {
|
|||||||
fn takes_a_date_format() {
|
fn takes_a_date_format() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -355,7 +377,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_iso8601_date_format() {
|
fn takes_iso8601_date_format() {
|
||||||
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
||||||
let actual = action(&date_str, &None, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -371,7 +398,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -388,7 +420,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_int, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_int, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -405,9 +442,14 @@ mod tests {
|
|||||||
item: Zone::Local,
|
item: Zone::Local,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Local.timestamp(1614434140, 0).into(),
|
val: Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -417,11 +459,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp_without_timezone() {
|
fn takes_timestamp_without_timezone() {
|
||||||
let date_str = Value::test_string("1614434140");
|
let date_str = Value::test_string("1614434140");
|
||||||
let timezone_option = None;
|
let args = Arguments {
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
val: Utc.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -435,7 +481,12 @@ mod tests {
|
|||||||
item: Zone::Utc,
|
item: Zone::Utc,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -444,7 +495,12 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,11 +15,16 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into decimal").rest(
|
Signature::build("into decimal")
|
||||||
"rest",
|
.input_output_types(vec![
|
||||||
SyntaxShape::CellPath,
|
(Type::String, Type::Number),
|
||||||
"optionally convert text into decimal by column paths",
|
(Type::Bool, Type::Number),
|
||||||
)
|
])
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, convert data at the given cell paths",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -36,13 +42,15 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer in table",
|
description: "Convert string to decimal in table",
|
||||||
example: "[[num]; ['5.01']] | into decimal num",
|
example: "[[num]; ['5.01']] | into decimal num",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![Value::Record {
|
vals: vec![Value::Record {
|
||||||
@ -54,56 +62,31 @@ impl Command for SubCommand {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer",
|
description: "Convert string to decimal",
|
||||||
example: "'1.345' | into decimal",
|
example: "'1.345' | into decimal",
|
||||||
result: Some(Value::test_float(1.345)),
|
result: Some(Value::test_float(1.345)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert decimal to integer",
|
description: "Convert decimal to decimal",
|
||||||
example: "'-5.9' | into decimal",
|
example: "'-5.9' | into decimal",
|
||||||
result: Some(Value::test_float(-5.9)),
|
result: Some(Value::test_float(-5.9)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert boolean to decimal",
|
||||||
|
example: "true | into decimal",
|
||||||
|
result: Some(Value::test_float(1.0)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(input: &Value, head: Span) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::String { val: s, span } => {
|
Value::String { val: s, span } => {
|
||||||
let other = s.trim();
|
let other = s.trim();
|
||||||
|
|
||||||
match other.parse::<f64>() {
|
match other.parse::<f64>() {
|
||||||
Ok(x) => Value::Float { val: x, span: head },
|
Ok(x) => Value::float(x, head),
|
||||||
Err(reason) => Value::Error {
|
Err(reason) => Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
"float".to_string(),
|
"float".to_string(),
|
||||||
@ -114,22 +97,25 @@ fn action(input: &Value, head: Span) -> Value {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Int { val: v, span } => Value::Float {
|
Value::Int { val: v, span } => Value::float(*v as f64, *span),
|
||||||
val: *v as f64,
|
Value::Bool { val: b, span } => Value::Float {
|
||||||
|
val: match b {
|
||||||
|
true => 1.0,
|
||||||
|
false => 0.0,
|
||||||
|
},
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
other => {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
let span = other.span();
|
Value::Error { .. } => input.clone(),
|
||||||
match span {
|
other => Value::Error {
|
||||||
Ok(s) => {
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
let got = format!("Expected a string, got {} instead", other.get_type());
|
"string, integer or bool".into(),
|
||||||
Value::Error {
|
other.get_type().to_string(),
|
||||||
error: ShellError::UnsupportedInput(got, s),
|
head,
|
||||||
}
|
// This line requires the Value::Error match above.
|
||||||
}
|
other.expect_span(),
|
||||||
Err(e) => Value::Error { error: e },
|
),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +137,7 @@ mod tests {
|
|||||||
let word = Value::test_string("3.1415");
|
let word = Value::test_string("3.1415");
|
||||||
let expected = Value::test_float(3.1415);
|
let expected = Value::test_float(3.1415);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data());
|
let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data());
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +145,11 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
||||||
let decimal_str = Value::test_string("11.6anra");
|
let decimal_str = Value::test_string("11.6anra");
|
||||||
|
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -168,7 +158,11 @@ mod tests {
|
|||||||
fn int_to_decimal() {
|
fn int_to_decimal() {
|
||||||
let decimal_str = Value::test_int(10);
|
let decimal_str = Value::test_int(10);
|
||||||
let expected = Value::test_float(10.0);
|
let expected = Value::test_float(10.0);
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath, Expr},
|
ast::{Call, CellPath, Expr},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Unit,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,10 +17,23 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::String, Type::Duration),
|
||||||
|
(Type::Duration, Type::Duration),
|
||||||
|
// TODO: --convert option should be implemented as `format duration`
|
||||||
|
(Type::String, Type::String),
|
||||||
|
(Type::Duration, Type::String),
|
||||||
|
])
|
||||||
|
.named(
|
||||||
|
"convert",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"convert duration into another duration",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to duration (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -28,6 +42,10 @@ impl Command for SubCommand {
|
|||||||
"Convert value to duration"
|
"Convert value to duration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"This command does not take leap years into account, and every month is assumed to have 30 days."
|
||||||
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "time", "period"]
|
vec!["convert", "time", "period"]
|
||||||
}
|
}
|
||||||
@ -47,7 +65,8 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to duration in table",
|
description: "Convert string to duration in table",
|
||||||
example: "echo [[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
|
example:
|
||||||
|
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::Record {
|
Value::Record {
|
||||||
@ -102,6 +121,30 @@ impl Command for SubCommand {
|
|||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert string to the requested duration as a string",
|
||||||
|
example: "'7min' | into duration --convert sec",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "420 sec".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert duration to duration",
|
||||||
|
example: "420sec | into duration",
|
||||||
|
result: Some(Value::Duration {
|
||||||
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert duration to the requested duration as a string",
|
||||||
|
example: "420sec | into duration --convert ms",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "420000 ms".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,17 +156,23 @@ fn into_duration(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
let float_precision = config.float_precision as usize;
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
action(&v, head)
|
action(&v, &convert_to_unit, float_precision, head)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
let r =
|
let d = convert_to_unit.clone();
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
let r = ret.update_cell_path(
|
||||||
|
&path.members,
|
||||||
|
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||||
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
return Value::Error { error };
|
||||||
}
|
}
|
||||||
@ -136,7 +185,151 @@ fn into_duration(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
fn convert_str_from_unit_to_unit(
|
||||||
|
val: i64,
|
||||||
|
from_unit: &str,
|
||||||
|
to_unit: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<f64, ShellError> {
|
||||||
|
match (from_unit, to_unit) {
|
||||||
|
("ns", "ns") => Ok(val as f64),
|
||||||
|
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||||
|
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
|
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||||
|
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||||
|
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("ns", "dec") => {
|
||||||
|
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||||
|
("us", "us") => Ok(val as f64),
|
||||||
|
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||||
|
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
|
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||||
|
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
|
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||||
|
("ms", "ms") => Ok(val as f64),
|
||||||
|
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||||
|
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||||
|
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||||
|
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
|
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||||
|
("sec", "sec") => Ok(val as f64),
|
||||||
|
("sec", "min") => Ok(val as f64 / 60.0),
|
||||||
|
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||||
|
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||||
|
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||||
|
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||||
|
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||||
|
("min", "sec") => Ok(val as f64 * 60.0),
|
||||||
|
("min", "min") => Ok(val as f64),
|
||||||
|
("min", "hr") => Ok(val as f64 / 60.0),
|
||||||
|
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||||
|
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||||
|
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||||
|
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||||
|
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||||
|
("hr", "min") => Ok(val as f64 * 60.0),
|
||||||
|
("hr", "hr") => Ok(val as f64),
|
||||||
|
("hr", "day") => Ok(val as f64 / 24.0),
|
||||||
|
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||||
|
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||||
|
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||||
|
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||||
|
("day", "hr") => Ok(val as f64 * 24.0),
|
||||||
|
("day", "day") => Ok(val as f64),
|
||||||
|
("day", "wk") => Ok(val as f64 / 7.0),
|
||||||
|
("day", "month") => Ok(val as f64 / 30.0),
|
||||||
|
("day", "yr") => Ok(val as f64 / 365.0),
|
||||||
|
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||||
|
|
||||||
|
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||||
|
("wk", "day") => Ok(val as f64 * 7.0),
|
||||||
|
("wk", "wk") => Ok(val as f64),
|
||||||
|
("wk", "month") => Ok(val as f64 / 4.0),
|
||||||
|
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||||
|
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||||
|
|
||||||
|
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||||
|
("month", "day") => Ok(val as f64 * 30.0),
|
||||||
|
("month", "wk") => Ok(val as f64 * 4.0),
|
||||||
|
("month", "month") => Ok(val as f64),
|
||||||
|
("month", "yr") => Ok(val as f64 / 12.0),
|
||||||
|
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||||
|
|
||||||
|
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||||
|
("yr", "day") => Ok(val as f64 * 365.0),
|
||||||
|
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||||
|
("yr", "month") => Ok(val as f64 * 12.0),
|
||||||
|
("yr", "yr") => Ok(val as f64),
|
||||||
|
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||||
|
|
||||||
|
_ => Err(ShellError::CantConvertWithValue(
|
||||||
|
"string duration".to_string(),
|
||||||
|
"string duration".to_string(),
|
||||||
|
to_unit.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
if let Expr::Int(x) = value.expr {
|
if let Expr::Int(x) = value.expr {
|
||||||
@ -155,25 +348,150 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ShellError::CantConvert(
|
Err(ShellError::CantConvertWithValue(
|
||||||
"duration".to_string(),
|
"duration".to_string(),
|
||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
span,
|
span,
|
||||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn string_to_unit_duration(
|
||||||
|
s: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<(&str, i64), ShellError> {
|
||||||
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
|
if let Expr::Int(x) = value.expr {
|
||||||
|
match unit.item {
|
||||||
|
Unit::Nanosecond => return Ok(("ns", x)),
|
||||||
|
Unit::Microsecond => return Ok(("us", x)),
|
||||||
|
Unit::Millisecond => return Ok(("ms", x)),
|
||||||
|
Unit::Second => return Ok(("sec", x)),
|
||||||
|
Unit::Minute => return Ok(("min", x)),
|
||||||
|
Unit::Hour => return Ok(("hr", x)),
|
||||||
|
Unit::Day => return Ok(("day", x)),
|
||||||
|
Unit::Week => return Ok(("wk", x)),
|
||||||
|
|
||||||
|
_ => return Ok(("ns", 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ShellError::CantConvertWithValue(
|
||||||
|
"duration".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(
|
||||||
|
input: &Value,
|
||||||
|
convert_to_unit: &Option<Spanned<String>>,
|
||||||
|
float_precision: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration {
|
||||||
Value::String { val, .. } => match string_to_duration(val, span) {
|
val: val_num,
|
||||||
Ok(val) => Value::Duration { val, span },
|
span: value_span,
|
||||||
Err(error) => Value::Error { error },
|
} => {
|
||||||
},
|
if let Some(to_unit) = convert_to_unit {
|
||||||
_ => Value::Error {
|
let from_unit = "ns";
|
||||||
error: ShellError::UnsupportedInput(
|
let duration = *val_num;
|
||||||
"'into duration' does not support this input".into(),
|
match convert_str_from_unit_to_unit(
|
||||||
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
|
span,
|
||||||
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String {
|
||||||
|
val,
|
||||||
|
span: value_span,
|
||||||
|
} => {
|
||||||
|
if let Some(to_unit) = convert_to_unit {
|
||||||
|
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
|
||||||
|
let from_unit = dur.0;
|
||||||
|
let duration = dur.1;
|
||||||
|
match convert_str_from_unit_to_unit(
|
||||||
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
|
span,
|
||||||
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::CantConvert(
|
||||||
|
"string".into(),
|
||||||
|
"duration".into(),
|
||||||
|
span,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match string_to_duration(val, span, *value_span) {
|
||||||
|
Ok(val) => Value::Duration { val, span },
|
||||||
|
Err(error) => Value::Error { error },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"string or duration".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -192,102 +510,110 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_ns_to_duration() {
|
fn turns_ns_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("3ns");
|
let word = Value::test_string("3ns");
|
||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_us_to_duration() {
|
fn turns_us_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("4us");
|
let word = Value::test_string("4us");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 4 * 1000,
|
val: 4 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_ms_to_duration() {
|
fn turns_ms_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("5ms");
|
let word = Value::test_string("5ms");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 5 * 1000 * 1000,
|
val: 5 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_sec_to_duration() {
|
fn turns_sec_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("1sec");
|
let word = Value::test_string("1sec");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 1000 * 1000 * 1000,
|
val: 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_min_to_duration() {
|
fn turns_min_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("7min");
|
let word = Value::test_string("7min");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_hr_to_duration() {
|
fn turns_hr_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("42hr");
|
let word = Value::test_string("42hr");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_day_to_duration() {
|
fn turns_day_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 5);
|
||||||
let word = Value::test_string("123day");
|
let word = Value::test_string("123day");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_wk_to_duration() {
|
fn turns_wk_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("3wk");
|
let word = Value::test_string("3wk");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user