forked from extern/nushell
Compare commits
4113 Commits
Author | SHA1 | Date | |
---|---|---|---|
5f11be69ed | |||
69765340f3 | |||
4be392fcb4 | |||
81531e224e | |||
370639d7d7 | |||
d326f6def6 | |||
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 | |||
06cf3fa5ad | |||
9a482ce284 | |||
8018ae3286 | |||
ef322a24c5 | |||
a8db4f0b0e | |||
98a4280c41 | |||
0e1bfae13d | |||
6ff717c0ba | |||
d534a89867 | |||
5bc9246f0f | |||
1e89cc3578 | |||
06f5199570 | |||
9e5e9819d6 | |||
1f8ccd8e5e | |||
e9d8b19d4d | |||
7c63ce15d8 | |||
a3a9571dac | |||
2cc5952c37 | |||
aa88449f29 | |||
06199d731b | |||
0ba86d7eb8 | |||
6efd1bcb3f | |||
0d06b6259f | |||
8fdc272bcc | |||
0ea7a38c21 | |||
1999e0dcf3 | |||
ac30b3d108 | |||
2b1e05aad0 | |||
6c56829976 | |||
2c58beec13 | |||
9c779b071b | |||
1e94793df5 | |||
7d9a77f179 | |||
bb079608dd | |||
5fa42eeb8c | |||
3e09158afc | |||
7a78171b34 | |||
633ebc7e43 | |||
f0cb2f38df | |||
f26d3bf8d7 | |||
498672f5e5 | |||
038391519b | |||
8004e8e2a0 | |||
e192684612 | |||
5d40fc2726 | |||
a22d70718f | |||
24a49f1b0a | |||
04473a5593 | |||
d1e7884d19 | |||
2b96c93b8d | |||
fc41a0f96b | |||
8bd68416e3 | |||
2062e33c37 | |||
c6383874e9 | |||
d90b25c633 | |||
44bcfb3403 | |||
c047fd4778 | |||
16bd7b6d0d | |||
3cef94ba39 | |||
f818193b53 | |||
1aec4a343a | |||
852de79212 | |||
06f40405fe | |||
65bac77e8a | |||
32d1939a95 | |||
53e35670ea | |||
a92567489f | |||
2145feff5d | |||
0b95465ea1 | |||
ec804f4568 | |||
4717ac70fd | |||
9969fbfbb1 | |||
5f39267a80 | |||
94a9380e8b | |||
1d64863585 | |||
8218f72eea | |||
c0b99b7131 | |||
75c033e4d1 | |||
d88d057bf6 | |||
b00098ccc6 | |||
7e5e9c28dd | |||
8ffffe9bcc | |||
8030f7e9f0 | |||
e4959d2f9f | |||
f311da9623 | |||
14d80d54fe | |||
23b467061b | |||
8d8f25b210 | |||
7ee22603ac | |||
4052a99ff5 | |||
ccfa35289b | |||
54fc164e1c | |||
3a35bf7d4e | |||
a61d09222f | |||
07ac3c3aab | |||
061e9294b3 | |||
374757f286 | |||
ca75cd7c0a | |||
d08c072f19 | |||
9b99b2f6ac | |||
1cb449b2d1 | |||
6cc66c8afd | |||
08e495ea67 | |||
b0647f780d | |||
2dfd975940 | |||
fbdb125141 | |||
c2ea993f7e | |||
e14e60dd2c | |||
768ff47d28 | |||
78a1879e36 | |||
0b9c0fea9d | |||
02a3430ef0 | |||
6623ed9061 | |||
48cf103439 | |||
1bcb87c48d | |||
da104050e6 | |||
d306b834ca | |||
d4371438d1 | |||
6a972312d4 | |||
ac48f5a318 | |||
e36649f74b | |||
1a52460695 | |||
ab98ecd55b | |||
9a8e939cbe | |||
bb27b9f371 | |||
1ca3063ac3 | |||
7c9a78d922 | |||
49cbc30974 | |||
07a7bb14bf | |||
74f1c5b67b | |||
3b0151aba6 | |||
4a69819f9a | |||
1f7d3498cd | |||
f0b9dc9da1 | |||
96f8691c8d | |||
07255e576d | |||
260be40774 | |||
14c9bd44ef | |||
92785ab92c | |||
98ab31e15e | |||
80d57d70cd | |||
8dc199d817 | |||
435693a8bb | |||
5077242892 | |||
7a7aa310aa | |||
07893e01c1 | |||
f16401152b | |||
3df03e2e6d | |||
7c6f976d65 | |||
ae9c0fc138 | |||
9da2e142b2 | |||
5999506f87 | |||
1fc7abcc38 | |||
2659ea3dbd | |||
fa27110651 | |||
b4f8798a3a | |||
7714956276 | |||
8e5cc655e9 | |||
c78e28511d | |||
f189369fd7 | |||
2516305fa8 | |||
f2d7454330 | |||
3cf3329e49 | |||
d2bc2dcbb2 | |||
4ec4649903 | |||
55e5106695 | |||
5f35e4ad1e | |||
e7831d38ae | |||
5c9fe85ec4 | |||
cd5199de31 | |||
5319544481 | |||
be3f0edc97 | |||
fb8f7b114e | |||
187f2454c8 | |||
3492d4015d | |||
190f379ff3 | |||
5c2bc73d7b | |||
aeed8670f1 | |||
b38f90d4c7 | |||
9771270b38 | |||
f6b99b2d8f | |||
ec611526ac | |||
cd2df83ddc | |||
3eb447030b | |||
f2a45b3eac | |||
e94d13da1b | |||
c20ba95885 | |||
8eab311565 | |||
e2b510b65e | |||
e6a70f9846 | |||
667eb27d1b | |||
b9eb213f36 | |||
cc78446ffd | |||
5ff2ae628b | |||
661283c4d2 | |||
ee29a15119 | |||
2a18206771 | |||
a26272b44b | |||
7e730e28bb | |||
96253c69fb | |||
ded9d1cedb | |||
d1cc70fc4a | |||
18c9b62b00 | |||
1295495758 | |||
e97ba9b74c | |||
09b972f1dc | |||
0fb6f8f93c | |||
995d8db1fe | |||
7e97be1dd4 | |||
b501db673a | |||
c0ce1e9057 | |||
4d7b86f278 | |||
f2d47f97da | |||
0de289f6b7 | |||
ae674bfaec | |||
76079d5183 | |||
409f1480f5 | |||
e206555d9d | |||
88ec4186ec | |||
dd1d9b7623 | |||
1314a87cb0 | |||
cf65f77b02 | |||
c9f05f074a | |||
7710317224 | |||
0a990ed105 | |||
a35b975d84 | |||
6e85b04923 | |||
4d31139a44 | |||
1bad40726d | |||
cb3276fb3b | |||
c17129a92a | |||
5bf1c98a39 | |||
13b371ab58 | |||
2a3991cfdb | |||
583b7b1821 | |||
581afc9023 | |||
8e2847431e | |||
6a1378c1bb | |||
2fe14a7a5a | |||
7490392eb9 | |||
9844e6125b | |||
56af7e8d5f | |||
dc612e7ffb | |||
1d1dbfd04c | |||
c150e11cb4 | |||
87c684c7da | |||
10792a29f7 | |||
257290acc2 | |||
cfefb65d55 | |||
3783c19d02 | |||
1f6e0255c0 | |||
b61af9a26a | |||
6e2f6c71fe | |||
f51d5789a3 | |||
933cee27ae | |||
4566c904d0 | |||
9b020c056b | |||
60b5863058 | |||
836f914163 | |||
594006cfa0 | |||
57761149f4 | |||
521e28dcdc | |||
a30930324d | |||
625e807a35 | |||
d18f34daa4 | |||
4fd73ef54a | |||
58f395989a | |||
791e8a0e59 | |||
14066ccc30 | |||
683b912263 | |||
3bac480ca0 | |||
97eb8492a3 | |||
0892a16a3d | |||
0b85938415 | |||
aaec840b91 | |||
74d0f19291 | |||
7ce570e52c | |||
3a0eded0b8 | |||
5afd45414e | |||
6ed033737d | |||
d38a3a8b4e | |||
6b4cb8b0e0 | |||
48fa25fd42 | |||
bdfad6b1de | |||
4f974efeba | |||
e86c1b118e | |||
5e177fe8e7 | |||
4129f15eb9 | |||
690ec9abfa | |||
b2c52b51b7 | |||
888369022f | |||
4409185e1b | |||
ef1934a7ee | |||
591fb4bd36 | |||
12d3e4e424 | |||
c3bed1352a | |||
3ceb39c82c | |||
13869e7d52 | |||
121a4f06fb | |||
d0e636ae7a | |||
6e7e2dbb97 | |||
d64cf1687e | |||
657b631fdc | |||
fa6ed7a40b | |||
80f21d37e0 | |||
e2cf4cc7d6 | |||
abe028f930 | |||
ef1cf7e634 | |||
1e4b33b9c6 | |||
4654f03d31 | |||
608b6f3634 | |||
a86e6ce89b | |||
c4cfbaec2d | |||
d40109f210 | |||
20be8a4987 | |||
d6dd4078b1 | |||
6649da3f5d | |||
62901573d0 | |||
80c9888f82 | |||
19c3570cf9 | |||
2cb815b7b4 | |||
a088081695 | |||
4bb95a880f | |||
9beecff736 | |||
fa0400c3f2 | |||
1d2d31580b | |||
834f993547 | |||
2193910579 | |||
f692da487e | |||
0986c61a5d | |||
6a6471b04b | |||
05f7d7d38b | |||
d89ad4fafd | |||
385bc40627 | |||
e2d24c5956 | |||
82633e2df7 | |||
31a4fc41eb | |||
79182db587 | |||
a2872b4ccc | |||
0afa18ac4a | |||
1aef3a730a | |||
e934062542 | |||
2e3b74f1b2 | |||
a87f53072a | |||
047081fa72 | |||
5586d4a0a0 | |||
911fba8a8a | |||
2873e943b3 | |||
0c9dd6a29a | |||
a4410fef40 | |||
0011f4df56 | |||
ee5064abed | |||
82e3bb0f38 | |||
319930a1b9 | |||
a64e0956cd | |||
91e17d2f9f | |||
56a546e73d | |||
cf88c8eef3 | |||
3484e0defd | |||
79e4d35f01 | |||
71dd857926 | |||
7a789d68a2 | |||
8a9cc33aac | |||
66087b01e6 | |||
19fa41b114 | |||
91cd1717e9 | |||
12b85beecc | |||
2252833917 | |||
4e9c1067fb | |||
e505e57a7a | |||
d122827a30 | |||
b007290a4e | |||
7c92791eed | |||
80769b7197 | |||
9b5dff828d | |||
90013295aa | |||
ea7c8c237e | |||
5d5b02d8dc | |||
00b67d338d | |||
d32e878868 | |||
41af2e4b30 | |||
e9f9aab79f | |||
e826540037 | |||
a435a9924c | |||
02ed15b932 | |||
81e269c483 | |||
eceae26b0a | |||
ec5fd62f9f | |||
74af31a42f | |||
1c964cdfe7 | |||
352cf31db2 | |||
66e736dab4 | |||
18067138aa | |||
bd7a506897 | |||
1d38ff071e | |||
e6a5011fdb | |||
d5f23ab592 | |||
bd5778fa24 | |||
f3bb1d11d3 | |||
285f91e67a | |||
01c1e5e8b0 | |||
d6669d3f33 | |||
3db608eb5c | |||
b293282e9b | |||
983d115bc0 | |||
5a1af4d661 | |||
4f05e9f4a6 | |||
7773c4cd4d | |||
d0cbb2d12c | |||
0986eefb64 | |||
6e69d40bb9 | |||
9db356e174 | |||
6700fbeed7 | |||
ca12f39db3 | |||
460d635ed0 | |||
1a16b9a2c4 | |||
0bd8664f33 | |||
762da0989c | |||
65baeaecd4 | |||
cb5d997adf | |||
10d805c1fa | |||
54d9fff4f2 | |||
6e65aef9bf | |||
72daf8c64e | |||
c023d4111a | |||
ff3dffd813 | |||
30bb090cd4 | |||
dfffd45bcd | |||
c73d8d5f95 | |||
0ff9cc679e | |||
b342270112 | |||
ccc85a2979 | |||
005301647a | |||
5fcc670860 | |||
90b2ec537f | |||
f3626f7c3a | |||
7debb27d78 | |||
675d30d980 | |||
2f70442165 | |||
ce690ed18f | |||
14dc662e50 | |||
ffa3aaaa56 | |||
2b3843c7c0 | |||
9abb14b5fd | |||
12bf23faa6 | |||
643cce8a6f | |||
3bdd924349 | |||
be43b3c5fc | |||
355b1d9929 | |||
0d82d7df60 | |||
8fcf51908a | |||
0835073d85 | |||
925e9f4dcb | |||
e0fac7bc72 | |||
fac086c826 | |||
088d19ad47 | |||
99f7636b03 | |||
2ac990655e | |||
4ddf24269a | |||
b73af3b8df | |||
dc0c5a9772 | |||
477f3be8df | |||
ae7c0b1097 | |||
cede9b3156 | |||
299fea8538 | |||
35ff1076f3 | |||
073f8655eb | |||
1837bf775c | |||
a3df2e5631 | |||
0a95bc7e60 | |||
a2723c2ba4 | |||
0b6b321ad6 | |||
4f43d75130 | |||
fbbbde1489 | |||
7701c6b1d4 | |||
5ae5ef5146 | |||
69fd777120 | |||
fa7d66347f | |||
4aa9a18c63 | |||
1527b34d9c | |||
a4a8f5df54 | |||
32601bb352 | |||
6a1e504a50 | |||
488f81d012 | |||
bc119a5e98 | |||
9c17c73d5f | |||
cd721fc363 | |||
5b3cc73ac6 | |||
02dfb57ed1 | |||
fbb2e7136c | |||
b714e034aa | |||
e64ca97fe2 | |||
eef3de2d05 | |||
89b7f4d57c | |||
7c205d7a3a | |||
1157fcf372 | |||
eeef9f27eb | |||
47d5501f9f | |||
97b3e4a233 | |||
52f4c4ba7e | |||
7d0531d270 | |||
13f2048ffb | |||
210d25f2a0 | |||
2fd42d25b1 | |||
d90b7953dd | |||
50399c349f | |||
96a1bf5f8d | |||
fd88920a9d | |||
88d7b50e37 | |||
0da9213de6 | |||
4965f4cbf4 | |||
fef7f38da8 | |||
42f1874a3a | |||
2a89936bee | |||
ece5e7dbb7 | |||
a6a96b29cb | |||
e3100e6afd | |||
cb5c61d217 | |||
b09acdb7f9 | |||
0924975b4c | |||
d6a6c4b034 | |||
eec1730449 | |||
10364c4f22 | |||
ef70c8dbe4 | |||
0f437589fc | |||
796d4920ab | |||
7819210037 | |||
4ebbe07d27 | |||
10ceac998e | |||
446c2aab17 | |||
995757c055 | |||
799fa98411 | |||
d2bd71d2aa | |||
11bc056576 | |||
3eca43c0bb | |||
ed46f0ea17 | |||
0c3ea636fb | |||
2b377391c2 | |||
c6a3066103 | |||
977ef66356 | |||
e6570b41ca | |||
2126bef052 | |||
e8a6458f0d | |||
2b1e4dd242 | |||
70009c015d | |||
cbad648d0e | |||
3c62d27c28 | |||
2c9d8c4818 | |||
c984ce9dc9 | |||
308ab91aff | |||
c3979ef1cf | |||
784382edde | |||
feb4f5c347 | |||
21c0f7d738 | |||
4b18fdcc6e | |||
63487941fb | |||
676457acd3 | |||
f507613b38 | |||
25712760ba | |||
d054a724d1 | |||
c2bad71123 | |||
b448d1dbe1 | |||
3e8a41fbc9 | |||
31925c3d40 | |||
9888f8f298 | |||
739e403cd5 | |||
359bb6eebe | |||
6d4784a7c1 | |||
e4dcdcb254 | |||
88fa40d698 | |||
24fc9c657e | |||
6670b77b27 | |||
c0a1d18e3d | |||
2e167ea0c6 | |||
41fa1ab656 | |||
53b5012f1e | |||
07cd8f483e | |||
d1ec05b12b | |||
a2c4c92fce | |||
917886f8ad | |||
4f367a59de | |||
968427c4e9 | |||
d454fad4dc | |||
a96f8b891e | |||
5befa6f80a | |||
5bf2ffeaf5 | |||
9b2a022f5b | |||
fd22211737 | |||
2ba12afb01 | |||
6024a17a5b | |||
56aacc4852 | |||
643c5097d6 | |||
52ee1917ba | |||
9ea5a2ecd3 | |||
a32ce93c79 | |||
b92aaf0432 | |||
2ecae0ef43 | |||
aea4355d04 | |||
a6c565ed4e | |||
7163721571 | |||
965cea3af5 | |||
efd62f917f | |||
ac99ac003a | |||
3ecf17e7af | |||
da42100374 | |||
dfc478e074 | |||
28b5399fb7 | |||
3f14b75153 | |||
0f4f660759 | |||
d53eaac7a1 | |||
f085bd97f6 | |||
c893cc1485 | |||
e5bf56a7dd | |||
06f9047be4 | |||
786e4ab971 | |||
f65955ccc5 | |||
1235d516a5 | |||
dd11be03be | |||
a967854332 | |||
a5f9ad2a43 | |||
1377693f0f | |||
bccce0ab46 | |||
c7c427723b | |||
d4cd3f9578 | |||
9415352447 | |||
8f5b857fcf | |||
fa75c93765 | |||
393cb7ca6f | |||
f5f9d56c37 | |||
d50ccdf083 | |||
6e733f49bc | |||
f169a9be3b | |||
b8b2737890 | |||
d620f76a21 | |||
b64ac9eb7b | |||
5b6156687e | |||
c4e1559f89 | |||
644435bfe3 | |||
bd96ce4e9c | |||
7e6430def0 | |||
e763a8dcef | |||
df07e8e410 | |||
f824388f63 | |||
f11fa99d30 | |||
56b3fc61a3 | |||
66669d7839 | |||
5c1a1be02b | |||
9114a2d31d | |||
84f85ff9ae | |||
a743db8e8f | |||
fbaafaa459 | |||
f3d3e819fb | |||
63a2c2bc2d | |||
8c0a2d3c15 | |||
06f5affc0b | |||
7a3aeaf080 | |||
3576350b4b | |||
0fc03dbb00 | |||
4fdfc76d04 | |||
a520599fa0 | |||
77eb4c4188 | |||
e82ffc4dee | |||
6fc082f6e9 | |||
560be6e73e | |||
c5e7bccee5 | |||
73f94105a5 | |||
94a0e3060a | |||
eceb2d5106 | |||
9829e449e3 | |||
baf6348e66 | |||
cc171b6ad4 | |||
0256e42e3b | |||
48f4766a5f | |||
8ccc8e445f | |||
1fd7b9ac38 | |||
b4b7524206 | |||
328f7e92a0 | |||
fcc13224c1 | |||
926177235c | |||
85d1a681c7 | |||
968ef1e953 | |||
a16e485cce | |||
a767fa369c | |||
886ed5ab2d | |||
e16d6ae00c | |||
ba4d8ae8c3 | |||
e6db37bc82 | |||
0e5f4d88c5 | |||
2e3b2a48ee | |||
5cf91cb30d | |||
28947ff9a9 | |||
c2118e7505 | |||
e1f98c1bfd | |||
12d4c2986c | |||
f275644e13 | |||
fc88a8538b | |||
5d18e07b7d | |||
5a1d81221f | |||
43850bf20e | |||
94ab981235 | |||
f9e1c4ef50 | |||
659da3c4a4 | |||
9c7feb2b19 | |||
cf20eed7bc | |||
6d303f2ca3 | |||
b16e72f0a5 | |||
56ba57c74a | |||
baceb54660 | |||
19caef260d | |||
565be6aaef | |||
7242e52faa | |||
101f4f62a8 | |||
5fabfda57b | |||
a660720b68 | |||
d70d91e559 | |||
10c4c50f1f | |||
dbcadbc12c | |||
fdce6c49ab | |||
9259a56a28 | |||
265ee1281d | |||
a78c82d811 | |||
3ab55f7de9 | |||
84d3620d9b | |||
8a373dd554 | |||
c3e0e8eb5c | |||
de4449c3ee | |||
796b7a1962 | |||
a911b21256 | |||
80306f9ba6 | |||
2dd32c2b88 | |||
3eba90232a | |||
c4858fb202 | |||
8a93548de2 | |||
e45e8109aa | |||
709927cee4 | |||
abaeffab91 | |||
73dcec8ea1 | |||
b26acf97bd | |||
f29dbeddd7 | |||
8204cc4f28 | |||
c2f6dfa75c | |||
90f6b6aedf | |||
ece1e43238 | |||
fefd5fef12 | |||
dd2d601471 | |||
c6dad0d5eb | |||
522a53af68 | |||
1a246d141e | |||
b86c6db400 | |||
1e86af2fb9 | |||
a008f1aa80 | |||
ac0b331f00 | |||
3d3298290a | |||
e1c28cf06b | |||
2f0bbf5adb | |||
0043b9da74 | |||
b9c2bf226f | |||
cc1b784e3d | |||
cbdc0e2010 | |||
004d7b5ff0 | |||
ebaa584c5e | |||
c80a15cdfe | |||
4c9df9c7c1 | |||
96fedb47ee | |||
b1aa8f4edf | |||
d62716c83e | |||
def5869c1c | |||
76a4455255 | |||
2fbd182993 | |||
67cb720f24 | |||
a51d45b99d | |||
1fd0ddb52c | |||
060a4b3f48 | |||
95a5e9229a | |||
3c8716873e | |||
44821d9941 | |||
bffb4950c2 | |||
dc6f1c496b | |||
65ae3160ca | |||
1a25970645 | |||
9450bcb90c | |||
e91d8655c6 | |||
4c029d2545 | |||
c37f844644 | |||
86eeb4a5e7 | |||
020ad24b25 | |||
3f9fa28ae3 | |||
e11ac9f6f8 | |||
fd9e380a1e | |||
bfb9822475 | |||
9926561dd7 | |||
267ff4b0cf | |||
04395ee05c | |||
6f4b7efd3e | |||
a4421434d9 | |||
e8b8836977 | |||
78b5da8255 | |||
83ec374995 | |||
8ee619954d | |||
cdc8e67d61 | |||
285f65ba34 | |||
3023af66fd | |||
1ca3e03578 | |||
f4c0538653 | |||
69954a362d | |||
0cecaf82b1 | |||
5c749fcc63 | |||
6e44012a2f | |||
988a873466 | |||
ec94ca46bb | |||
53f41c1985 | |||
12189d417b | |||
0875d0451b | |||
62e9698b11 | |||
3d0b1ef1ce | |||
525ed7653f | |||
8a1b2d0812 | |||
d4fb95a98c | |||
f82e2fbac6 | |||
e11a030780 | |||
4e171203cc | |||
be0d221d56 | |||
fd3eec81b5 | |||
3d40e169ce | |||
bf9340ec48 | |||
310ecb79b6 | |||
89d852f76c | |||
af52def93c | |||
6a446f708d | |||
afe83104c6 | |||
b58aad5eb0 | |||
47d004ae24 | |||
446f160320 | |||
564c2dd7d1 | |||
e1272f3b73 | |||
6fa022b0a8 | |||
2df37d6ec2 | |||
0651e2b31f | |||
0ef0277882 | |||
939745ad67 | |||
f44954da68 | |||
846a048bba | |||
057bfff0cb | |||
ac07d93b02 | |||
91883bd572 | |||
69b2ed5566 | |||
b4e61a056c | |||
724cfaa890 | |||
65ef7b630b | |||
45b3592739 | |||
33ffb2c39a | |||
d4b6b4b09a | |||
54ed82a19a | |||
be8c905ca7 | |||
d2d22815fb | |||
6514a30b5d | |||
73ad862042 | |||
71feacf46c | |||
db704ebaed | |||
ff9d88887b | |||
4e8e03867c | |||
49e8af8ea5 | |||
d5d61d14b3 | |||
6d554398a7 | |||
60cbb7e75d | |||
efd9c5c7c3 | |||
f562a4526c | |||
e6c09f2dfc | |||
73a68954c4 | |||
20eb348896 | |||
2c75aabbfc | |||
01e691c5ba | |||
ac36f32647 | |||
085a7c18cb | |||
0f85646d8e | |||
c55b6c5ed5 | |||
283a615ecc | |||
9b128b7a03 | |||
bfe3c50dce | |||
5fae96a6b1 | |||
3b4baa31b6 | |||
746641edae | |||
fa5aab8170 | |||
b78924c777 | |||
75db4a75bc | |||
8f4ee14d85 | |||
89d99db94f | |||
f9c0d223c1 | |||
21a7278259 | |||
bee5ba3deb | |||
a7241f9899 | |||
40484966c3 | |||
7c23ae5cb0 | |||
ca215c1152 | |||
2b6ce4dfe5 | |||
bc1e1aa944 | |||
1ecbebb24a | |||
82d90f4930 | |||
d0f9943709 | |||
58c5ea4937 | |||
186da4d725 | |||
47495715a6 | |||
ffb086d56f | |||
74fd78e02c | |||
160339bd1f | |||
d3bfc61524 | |||
733b2836f1 | |||
3a17b60862 | |||
7970e71bd4 | |||
b49885bb85 | |||
4860014cec | |||
476d543dee | |||
d63eac69e5 | |||
38e0527083 | |||
3b467bedd9 | |||
f964ce9bc0 | |||
f016a5cb72 | |||
3478f35330 | |||
eab6b322bb | |||
8a0d2b4e32 | |||
e44789556b | |||
d39e8c15fe | |||
47544ad219 | |||
d0c280f6cc | |||
14cd798f00 | |||
cc1ae969fe | |||
3c2a336ef9 | |||
f71e16685c | |||
058738c48c | |||
affb9696c7 | |||
c158d29577 | |||
b4c72e85e1 | |||
41dbc641cc | |||
4584d69715 | |||
74dcd91cc3 | |||
8f6843c600 | |||
4d1ce6c27b | |||
857ecda050 | |||
36079f1a3d | |||
b6fcd46075 | |||
cb8b7e08a5 | |||
681e37cec6 | |||
91d807b1d2 | |||
fe5f65a247 | |||
9535e2c309 | |||
398502b0d6 | |||
850f66aa9d | |||
354d51a3a6 | |||
c9dcd212ba | |||
ffaaa53526 | |||
f7e3d4de24 | |||
a56994ccc5 | |||
ac487dfcbc | |||
4383b372f5 | |||
7fa1ad010b | |||
5d58f68c59 | |||
f734995170 | |||
44791b5835 | |||
15b979b06e | |||
18ddcdcb97 | |||
2320987862 | |||
822309be8e | |||
15b0424d73 | |||
56ae07adb9 | |||
80649f2341 | |||
7faa4fbff4 | |||
7d1d6f075c | |||
832a801c11 | |||
c8330523c8 | |||
62011b6bcc | |||
e94b8007c1 | |||
0c1a7459b2 | |||
384ea111eb | |||
5c94528fe2 | |||
53330c5676 | |||
1837acfc70 | |||
1dbf351425 | |||
f50f37c853 | |||
3706bef0a1 | |||
de30236f38 | |||
e1c92e90ca | |||
39f03bf5e4 | |||
e62e0fb679 | |||
89a000a572 | |||
ca6baf7a46 | |||
d603086d2f | |||
a811eee6b8 | |||
1efae6876d | |||
1214cd57e8 | |||
3522bead97 | |||
7f0921a14b | |||
b719f8d4eb | |||
29c8b826d4 | |||
ba1ff4cf6c | |||
5c83f4d405 | |||
f3c175562d | |||
c33104c4ae | |||
ef59b4aa51 | |||
3389baa392 | |||
5d3b63fa90 | |||
6cd124ddb2 | |||
061c822c5d | |||
0c920f7d05 | |||
43dd0960a0 | |||
9fb12fefb0 | |||
8ba3e3570c | |||
ea6912c3f7 | |||
deeb1da359 | |||
52dba91e1a | |||
266fac910a | |||
3ad5d4af66 | |||
a93a9b9029 | |||
6a35e6b7b6 | |||
c3a16902fe | |||
fc7ed1bfe4 | |||
d32aec5906 | |||
1609101e62 | |||
0571a6ee34 | |||
152467a858 | |||
caf73c36f2 | |||
e949658381 | |||
ff5b7e5ad2 | |||
cf5048205f | |||
c37bdcd119 | |||
038ad951da | |||
2883d6cd1e | |||
b54e9b6bfd | |||
ebf57c70e0 | |||
00bb203756 | |||
8933dde324 | |||
b3b328d19d | |||
46b86f3541 | |||
d8847f1082 | |||
ada9c742c6 | |||
6f6340186a | |||
6ba1e6172c | |||
438c2df8b6 | |||
6a0f404558 | |||
342584e5f8 | |||
efb4a9f95c | |||
bf6780967b | |||
a148ad8697 | |||
9a864b5017 | |||
17a7a85c78 | |||
89e2169521 | |||
e289630920 | |||
1d74d9c5ae | |||
aea2adc44a | |||
0450cc25e0 | |||
e9525627e6 | |||
1cbb785969 | |||
a41ae72bc1 | |||
a5c1dd0da5 | |||
e919f9a73b | |||
a3c349746f | |||
04a9c8f3fd | |||
673fe2b56a | |||
b5f8f64d79 | |||
930cb26e99 | |||
3701fd1d76 | |||
ee6ab17fde | |||
486f91e3a7 | |||
906c0e6bca | |||
1336acd34a | |||
2013e9300a | |||
90ddb23492 | |||
bee7ef21eb | |||
1576b959f9 | |||
4096f52003 | |||
7ceb668419 | |||
420aee18ca | |||
d1d1402512 | |||
c33d082ecc | |||
6f53912655 | |||
34a8a897c5 | |||
4d7dd23779 | |||
f8e6620e48 | |||
7cbeebaac1 | |||
9d7685e565 | |||
c2aa6c708d | |||
626b1b99cd | |||
4103abc685 | |||
d0119ea05d | |||
3df5e63c05 | |||
e77c6bb284 | |||
95841e3489 | |||
c2c4a1968b | |||
2e2d5ef0eb | |||
7a892ec5d7 | |||
865906e450 | |||
7319b6b168 | |||
c3b6e07de6 | |||
5c27ffa42e | |||
3dc19d4179 | |||
a8e5cb871e | |||
15e9c11849 | |||
9fd680ae2b | |||
ad94ed5e13 | |||
a7a213b3f2 | |||
1bdcdcca70 | |||
512dcf0988 | |||
8d027a0617 | |||
610e3911f6 | |||
11a781fc36 | |||
a42bbea98d | |||
c8b9913718 | |||
1fd26727c5 | |||
ee9eddd851 | |||
fdde95f675 | |||
9548e5ef5b | |||
29efbee285 | |||
22469a9cb1 | |||
03e22b071a | |||
71a8eb6f8e | |||
ddd8c3d9dc | |||
c6aff972da | |||
82aa84706e | |||
3e0c5e55b6 | |||
8a06ea133b | |||
eed22605ef | |||
8cf4402e6c | |||
df5ac9b71c | |||
bef138232c | |||
ee45755ea9 | |||
405a4e58c7 | |||
f3c8d35eb7 | |||
a28d38b05f | |||
574d7f6936 | |||
3d8394a909 | |||
349e83abd0 | |||
bf82417d52 | |||
c5297d2b64 | |||
d9bedaae2f | |||
19766556f3 | |||
687fefd791 | |||
ccd5f59314 | |||
c08e145501 | |||
ff673ba0ba | |||
c00853a473 | |||
f57d629b55 | |||
43972db131 | |||
f2aa952e86 | |||
071066b6d9 | |||
79c7b20cfd | |||
ac2afab40b | |||
99de2b1d77 | |||
45eba8b922 | |||
56307553ae | |||
2bbba3f5da | |||
34e0fd622b | |||
124561ff12 | |||
89cbfd758d | |||
d8c721282b | |||
d2a1564b94 | |||
7cf96c6597 | |||
b8f1fea7fe | |||
e6e6b730f3 | |||
0fe6a7c1b5 | |||
3916ac4165 | |||
c17e1473db | |||
21ddfc61f4 | |||
ce4d9dc7c6 | |||
414ed4877a | |||
5de12da765 | |||
1794ad51bd | |||
bab8f6bd28 | |||
ee239a0d37 | |||
e07ce57423 | |||
6d58e2b51e | |||
c8b16c14d5 | |||
e1e7e94261 | |||
8c0fa0d26e | |||
f7f8b0dbff | |||
63c3d19c67 | |||
0ba0daa2c4 | |||
fb197f562a | |||
91c270c14a | |||
5d88ed6c75 | |||
f052b3313d | |||
3e93ae8af4 | |||
8043516d75 | |||
6a1942b18f | |||
76019f434e | |||
a2aaeb38ed | |||
143855b662 | |||
d30dfc63c4 | |||
250743f60f | |||
e06df124ca | |||
00aac850fd | |||
e01e73cb67 | |||
ff43ca4d24 | |||
88988dc9f4 | |||
aa7226d5f6 | |||
adb7eeb740 | |||
96bdcc4ff7 | |||
f8f437b060 | |||
b35914bd17 | |||
2590fcbe5c | |||
09691ff866 | |||
6fbe02eb21 | |||
5459d30a24 | |||
16db368232 | |||
df87d90b8c | |||
f2f01b8a4d | |||
6c0190cd38 | |||
b26246bf12 | |||
36a4effbb2 | |||
ab22619f4a | |||
9fca417f8c | |||
d09e1148b2 | |||
42367ddf6d | |||
e324c1a078 | |||
4fd020ab7f | |||
50cbd16ec7 | |||
be827e5628 | |||
f1b2ab0b27 | |||
0f107b2830 | |||
493bc2b1c9 | |||
74b812228c | |||
e76451866d | |||
08d316f6a7 | |||
14a2918bba | |||
db2bca56c9 | |||
e756a9ea04 | |||
568e566adf | |||
586c6d9fa8 | |||
f5b20f0e3b | |||
75cfee28b2 | |||
d094f654c3 | |||
bb1740d733 | |||
0f516a0830 | |||
ef20b5f1ef | |||
e1468c0440 | |||
6f4993618d | |||
2103294d11 | |||
9c3c7b82c8 | |||
0a20052799 | |||
34617fabd9 | |||
47628946b6 | |||
ce714f098f | |||
066afb059e | |||
e9a7def183 | |||
e0a26cd048 | |||
b5bade6187 | |||
fcee3c65bd | |||
19645575d6 | |||
dd6452dfaa | |||
cfd40ffaf5 | |||
00a8752c76 | |||
7e070e2e5b | |||
573cb38bab | |||
a1f141d18a | |||
6c31377c21 | |||
d401ed64ed | |||
02b8027749 | |||
c7d159a0f3 | |||
649b3804c1 | |||
345b51b272 | |||
5837cdb3f1 | |||
183d200b9f | |||
8c43f60e2e | |||
f8e48aa0af | |||
44fad9e698 | |||
14f30287f1 | |||
ae1109139d | |||
1d356276c2 | |||
4a1df604c9 | |||
d23929fc80 | |||
df6a53f52e | |||
cfd24bc2ad | |||
f6d7df5a45 | |||
2b03748681 | |||
1949ba080e | |||
260838e5ea | |||
112ebe1842 | |||
47ebde4087 | |||
bfae75ca2e | |||
806cd4851f | |||
ea27300ca0 | |||
d3e5c5a342 | |||
5ae823612f | |||
20f3b8b274 | |||
6906de7c48 | |||
bf6c3e53a0 | |||
af5799c702 | |||
756773a6ed | |||
8192ba8d88 | |||
86e1092785 | |||
e193bf43fb | |||
12eed1f98a | |||
d134774f4b | |||
dfb846dec6 | |||
5e42b14026 | |||
78cc3452df | |||
070067b75e | |||
52cb50b937 | |||
b53570ceaa | |||
ce54764bea | |||
6e49d0f84b | |||
e1ea0d42a9 | |||
8368b52ac7 | |||
19301751ee | |||
7b2116dc29 | |||
732ff317f0 | |||
25846d3c1e | |||
bc8d90672e | |||
d856cebebd | |||
3c1b3473ae | |||
89b8ee6ad8 | |||
4a68c989e4 | |||
e16b0e7b01 | |||
e14945fdf5 | |||
1c2741c598 | |||
89225cf55c | |||
1f4c34fa04 | |||
f4ed4fa7e3 | |||
c56a233808 | |||
468b9affde | |||
ef94c71866 | |||
43c3cfecf7 | |||
3176f60b5b | |||
ef56d482b2 | |||
304c7a0c92 | |||
8707fbee33 | |||
032356bfb7 | |||
3437dacf0b | |||
80a4a5eb28 | |||
b340672331 | |||
73ae3daf85 | |||
f182524298 | |||
b7c0ba104f | |||
7112664b3f | |||
5add6035a4 | |||
a390f66dbf | |||
fa8a0958e4 | |||
20c770370b | |||
c4af5df828 | |||
e549c1112b | |||
f94a3e15f5 | |||
da515b1c9d | |||
37f7a36123 | |||
9838154ad1 | |||
f301f686b5 | |||
2dcfecbbd7 | |||
751595e72e | |||
a160d480b1 | |||
cf3f3fde92 | |||
6e6df46469 | |||
624edce4f7 | |||
75782f0f50 | |||
51e48bee53 | |||
d853127c2e | |||
520d9e1fb6 | |||
37150af970 | |||
bac8b8a450 | |||
40ad9acbc3 | |||
1308eb45d5 | |||
f92e9d25a5 | |||
4fc533340b | |||
c998bbbe6d | |||
c114f41545 | |||
9baf720156 | |||
4b31fe1924 | |||
656e86a7ca | |||
5d62f1a9c1 | |||
6d6b850911 | |||
b5329fe4ec | |||
78256b4923 | |||
bd6c550470 | |||
95628bef16 | |||
ca7ff37697 | |||
af02c8f6ea | |||
0f27249319 | |||
1eb93e5c07 | |||
2b06ce27d3 | |||
3625324bad | |||
7e66aca18e | |||
595fc7a7f6 | |||
402a4acd7a | |||
a240aead8c | |||
75b3b3e090 | |||
5163dbb7a1 | |||
cbda1b1650 | |||
e66fd91045 | |||
a29c333cb1 | |||
0c7ec03ba0 | |||
6b14f9d6b0 | |||
29dde84394 | |||
543c566ccc | |||
9995cbc03b | |||
abb6d9f10f | |||
e039e5f6a4 | |||
9b67899f8d | |||
5455270446 | |||
11d8e6c71f | |||
2ce034d0f0 | |||
017b1d8996 | |||
a2c2a07638 | |||
3a5b943d11 | |||
766726d0fa | |||
798552b1b3 | |||
df07ed5bf6 | |||
301d1f6f87 | |||
962adf5a12 | |||
c18f0dcc84 | |||
4be61ce604 | |||
85a69c0a45 | |||
d29208dd9e | |||
f84582ca2b | |||
5d19017603 | |||
3f313da4c3 | |||
baac60a5a7 | |||
b5965ee8ef | |||
397a31e69c | |||
b6d269e90a | |||
aa5ab8a666 | |||
ab9d6b206d | |||
ffb361833b | |||
36a834c1e3 | |||
90c750285c | |||
4bb2406772 | |||
4887b5e4fd | |||
1296100d31 | |||
5a1d99cefb | |||
232790f488 | |||
297f3ba575 | |||
7bd5f887d1 | |||
66dad40092 | |||
72c241348b | |||
ab2d2db987 | |||
51bea2e884 | |||
b1d7e3aa49 | |||
1c52919103 | |||
b322a12f58 | |||
07e05ef183 | |||
11070ffbbe | |||
7ef5a7945f | |||
e330fdabb7 | |||
c9439c962b | |||
b7ad4dc78a | |||
1b745015c3 | |||
2be26127c9 | |||
68601629c0 | |||
82b0415d92 | |||
bd5009a865 | |||
5bd20e4d36 | |||
28b26ca44d | |||
b3192ddc97 | |||
8c2ae1eed1 | |||
9807b4a484 | |||
fdf6bbb6fc | |||
68b7aa9470 | |||
0d7b10fd0b | |||
9ea7cdfc33 | |||
87d57108e6 | |||
dcda7a4e50 | |||
fdd2c35fd9 | |||
ff6cc2cbdd | |||
5c46138563 | |||
ef58348ea2 | |||
e473bdb26d | |||
9b60f76d04 | |||
a760e46c1c | |||
f5ce63ad55 | |||
151bdc8910 | |||
2b99e49792 | |||
94d00b28b7 | |||
8fee0b32e7 | |||
97d7157773 | |||
ffd922f393 | |||
1ea124a65b | |||
6024a001b4 | |||
67b8438bda | |||
270e4fdd4c | |||
aea8627c30 | |||
5f14faf4b4 | |||
60f0394106 | |||
c8277a3da9 | |||
9f02307bd7 | |||
96419f168b | |||
1f45304cf9 | |||
db62bce6aa | |||
63e3552eef | |||
ce81cd6e2f | |||
0d031636a9 | |||
1a15f30eb8 | |||
6e92812cdf | |||
0676f32509 | |||
576471cc3c | |||
e0433076ff | |||
a67be074e6 | |||
dada7a9867 | |||
ea9aad9b5d | |||
38bc394a12 | |||
020143d050 | |||
d33a9549b5 | |||
c4fe190cee | |||
ba73e0eb06 | |||
0504a7a776 | |||
1b9b709dec | |||
0e36b4b1bd | |||
acb0360180 | |||
4d0a253924 | |||
c3a032950d | |||
b4344b3964 | |||
491efab09b | |||
7cafdc9675 | |||
89267df9eb | |||
ecee5a9845 | |||
77c520e10b | |||
40741254f6 | |||
0b35905ce9 | |||
beb15dcc77 | |||
97ca242634 | |||
a986de8ad0 | |||
53f3d2572c | |||
a0a63c966f | |||
d5fdfdb614 | |||
75de7f7e61 | |||
4e443b2088 | |||
9e7e8ed48f | |||
5f9ad0947d | |||
4235cf1191 | |||
357b9ccaa9 | |||
d1f0740765 | |||
29cbcb8459 | |||
7f06d6144f | |||
7db6b876ab | |||
d3bc096d47 | |||
8783cf0138 | |||
563a0b92b5 | |||
8df9ea6c68 | |||
b28f876095 | |||
5d36d37d20 | |||
789fc30bf9 | |||
2c01901fcf | |||
e4ce41ba15 | |||
a1bfa2788c | |||
8756e88e3c | |||
41366f6cc4 | |||
e3e4ae0591 | |||
9a3b0312d2 | |||
2cd1f634d0 | |||
ddea4b416d | |||
5c29a83a7a | |||
60f9fe1aa4 | |||
44fbf0fce3 | |||
4ddc953e38 | |||
6c5a99b1a5 | |||
64d83142c3 | |||
b654415494 | |||
dea9c1482b | |||
c79dca999c | |||
1b977c658c | |||
ed9fa2b4c3 | |||
42113a767a | |||
7f3f41ff14 | |||
c636c30a19 | |||
5ddf0d209d | |||
1a3a837f3e | |||
c4dabe8327 | |||
a2eba38e81 | |||
bdfe8c0888 | |||
c4977ae143 | |||
54a41c535b | |||
8550f50522 | |||
e8e1ead99d | |||
adabc839bf | |||
698f768a06 | |||
ae8b315e76 | |||
58d73d4c23 | |||
22cfe4391e | |||
5021d61800 | |||
97d17311f4 | |||
06d819ecc8 | |||
bb126e8e09 | |||
0f6fd30619 | |||
248decc546 | |||
2500f23fcb | |||
7eb022b58c | |||
d481d5ca96 | |||
996ee363b7 | |||
011ad2e4e6 | |||
d6d0bad7aa | |||
b35d47c500 | |||
b3b51a2ed6 | |||
c7de1ee13a | |||
cc8a470668 | |||
74d4c501a8 | |||
5cc7fbcde7 | |||
48f534cd3b | |||
8536c12bd9 | |||
8dc3ebd6e2 | |||
5da1310696 | |||
9d49618e87 | |||
7697f7bdce | |||
e1ebd461d2 | |||
f000d5d0a1 | |||
51a43f5617 | |||
11b40a6c31 | |||
3c843f7f61 | |||
e402adbba0 | |||
c4ea398160 | |||
27dcbe5c8a | |||
4eb43adef2 | |||
0ef0588e29 | |||
80e7a8d594 | |||
1b96da5e5b | |||
e3ce58475b | |||
31ce8c1e33 | |||
14426433aa | |||
535ece4e76 | |||
574c5961c8 | |||
75ec0d123a | |||
c884d5ca31 | |||
f80e9d4b60 | |||
26166192e5 | |||
7c2bf68d45 | |||
6f5f1fa43a | |||
58b0e571d3 | |||
a88058006a | |||
1e1e12b027 | |||
9737d4a614 | |||
0fe525de87 | |||
4dacfaa44a | |||
b2148e32b8 | |||
e325fd114d | |||
dfd321a679 | |||
909b7d2160 | |||
2b5cc63118 | |||
75e323ee35 | |||
758fce8ae3 | |||
91090e1db1 | |||
5bf51b5a7a | |||
69708f7244 | |||
1d7ab28a0f | |||
eba3484611 | |||
b5ec9e0360 | |||
0cc121876b | |||
e4e1b7a11e | |||
be68b84473 | |||
ae34a34e00 | |||
81cd03626d | |||
6f4df31927 | |||
03339beae1 | |||
62c5df5fc6 | |||
92c855a412 | |||
9a64f1bff3 | |||
63a0aa6088 | |||
a8f9e6dcc2 | |||
6b76dd7cd7 | |||
7899745c4b | |||
5843acec02 | |||
9e7285ad46 | |||
8ef16c6da6 | |||
e1a0ad2987 | |||
2d4e471052 | |||
16c60f44d5 | |||
adb92b970e | |||
6595c06598 | |||
3567bbbf32 | |||
c5e9ff5f14 | |||
2c1b074bdc | |||
fb0f83e574 | |||
891d79d2aa | |||
25b05dec9e | |||
2af8116f50 | |||
aa06a71e1f | |||
8ed6afe1e5 | |||
244289c901 | |||
7488254cca | |||
3cbf99053f | |||
93521da9d8 | |||
561feff365 | |||
1b89ccf25b | |||
5b3b74ebec | |||
a16144baf1 | |||
d395816929 | |||
5a5205d5d9 | |||
503939dcbe | |||
000db46618 | |||
d6e24cceb4 | |||
99666829e0 | |||
db3e9efc4b | |||
3e232a5db8 | |||
e00755a2e9 | |||
d34e083976 | |||
8250b44ce5 | |||
f0d5e2dcf1 | |||
5e34ef6dff | |||
d567c58cc1 | |||
125c8c82c3 | |||
4e0d7bc77c | |||
2b5ef1b2d7 | |||
719920fa37 | |||
3b134a1ae2 | |||
84d0e0a059 | |||
0a48bc973d | |||
0108a935ed | |||
5ccbf4df67 | |||
9ee4dc49ee | |||
756269ee8d | |||
abb0d7bd22 | |||
47421e9ca7 | |||
3f8f3ecf9a | |||
f57f7b2def | |||
9e176674a5 | |||
57a07385ac | |||
12cf1a8f83 | |||
e9f1575924 | |||
32581497ef | |||
1015ea814c | |||
abac7e3795 | |||
22c6ed4718 | |||
3421a8b58b | |||
75510b172a | |||
04a8280d51 | |||
139775dcce | |||
d9c42eb194 | |||
6387401041 | |||
dadc354847 | |||
25a776c36b | |||
637e4f6e6d | |||
b12a265f1e | |||
cf60f72452 | |||
a176f12c9e | |||
d6df367c6b | |||
a2996abd47 | |||
b8d218e65b | |||
4e6327de1d | |||
b3d8666db0 | |||
d1da75d315 | |||
1de7c3d033 | |||
767d822cbf | |||
b4977f1515 | |||
6c589affe7 | |||
cb9db792a6 | |||
04990eeba4 | |||
772f8598dd | |||
95439f5e9c | |||
36c32e9832 | |||
660e8b5b73 | |||
5d442a287f | |||
962b258cc6 | |||
59697cab63 | |||
984538555c | |||
0ccbebee7a | |||
9f9bec38e1 | |||
d1474c0691 | |||
673137be8b | |||
180dafb84c | |||
2553da3dc4 | |||
a7ecf7af90 | |||
ff1adbd346 | |||
32f39c2fb8 | |||
923330aadd | |||
c87414e462 | |||
dbfd2808ba | |||
3c18cac134 | |||
4841d62d76 | |||
e5aa8b9d3f | |||
a1d6cefdf8 | |||
d532f3f304 | |||
349af05da8 | |||
29771c7d23 | |||
cb0914ecb0 | |||
b3b3cf0689 | |||
672dd5a868 | |||
cbe85cbeaf | |||
6731e3542d | |||
5d59234f8d | |||
5a6aebfcb2 | |||
96af23f370 | |||
4e6b6a8902 | |||
bafc50fd5c | |||
4f7b423f36 | |||
f7043bf690 | |||
1297499d7a | |||
bd0baa961c | |||
4ee536f044 | |||
8581bec891 | |||
8bcbc8eeb3 | |||
c164ef5489 | |||
cc3653cfd9 | |||
c03324ec9c | |||
7fc65067cf | |||
f9ae882012 | |||
1d80a68f4c | |||
1a9247b77f | |||
22e30d5ea7 | |||
b4f918b889 | |||
7b54e5c4ab | |||
c4d1c458a2 | |||
fda69354db | |||
7aa1d8ac2a | |||
b6fdf611f6 | |||
c0bad7ab23 | |||
d7a3c7522b | |||
4dfde7393b | |||
32c1f0c8d4 | |||
eb67eab122 | |||
d88e46d2d1 | |||
caa6236f1f | |||
f459f77335 | |||
66c58217af | |||
8f07f40f22 | |||
e6a2e27e33 | |||
8577d3ff41 | |||
78054a5352 | |||
ce0b5bf4ab | |||
9936946eb5 | |||
013b12a864 | |||
2f04c172fe | |||
cc5c4d38bb | |||
648fe052db | |||
55aa70c88a | |||
aa7ebdc9ce | |||
9c98783917 | |||
4b8ba29cdb | |||
4749776984 | |||
47ee50072e | |||
198c884158 | |||
1d945d8ce3 | |||
2d3a56f0d3 | |||
bfd05772ef | |||
9a16a8fd06 | |||
2ea19aeac0 | |||
0794ebf5fa | |||
a8ba00b250 | |||
26d50ebcd5 | |||
0fa0c25fb3 | |||
0694245ccd | |||
c1194b3d1e | |||
16baf5e16a | |||
55eafadf02 | |||
51c74eebd0 | |||
5edcf3910d | |||
abda6f148c | |||
f7333ebe58 | |||
6b2f639095 | |||
bb6781a3b1 | |||
b821b14987 | |||
56b3f119c0 | |||
4ee1776ceb | |||
90204bd0c8 | |||
2d7192e390 | |||
1e09a8e5ff | |||
85a45ccf6a | |||
d35a58e05c | |||
5605678bab | |||
ecbe7bf8d7 | |||
1e4146aec5 | |||
3990120813 | |||
fa84205e31 | |||
6dd9f05ea1 | |||
ab3820890b | |||
8e8ef83875 | |||
ed6abced5b | |||
2904002008 | |||
eccf0b9903 | |||
a8646f94ab | |||
71bbd70a57 | |||
6af3affee2 | |||
b0ab78a767 | |||
2055b83c34 | |||
e00da070fd | |||
b8e8061787 | |||
bdce34676a | |||
8f54ba10aa | |||
8db844a8d0 | |||
3b7d7861e3 | |||
f71b7e89e0 | |||
f7a19d37c6 | |||
c027a14b9b | |||
f91d0d6d65 | |||
4ce9a5c894 | |||
7191b08903 | |||
3534bd8a64 | |||
cdbd333c9b | |||
a1f7a3c17b | |||
76c92fc706 | |||
9b56221b5c | |||
3b99ce71a0 | |||
5f4cc50ce7 | |||
96b0edf9b0 | |||
9e7d96ea50 | |||
faa53de893 | |||
b930fc5d9d | |||
979faf853a | |||
aaee3a8b61 | |||
036c6a9a52 | |||
b3d287815d | |||
fda7e096cd | |||
57677a50b5 | |||
6f17695891 | |||
6ebc97dec2 | |||
56c8987e0f | |||
7ae4ca88b6 | |||
f0d469f1d4 | |||
6b4fee88c9 | |||
672fa852b3 | |||
0b412cd6b3 | |||
ae9f4135c0 | |||
2794556eaa | |||
a26c42a9b6 | |||
331ccd544f | |||
d6b1ff932a | |||
26b1f022b7 | |||
ab307c8d38 | |||
a3d4794341 | |||
25c7d8ead6 | |||
2834d71a12 | |||
bf9b6d8088 | |||
d9cff4238d | |||
198a36b744 | |||
f259992b4b | |||
ca8d311c78 | |||
acc035dbef | |||
5e33b8536b | |||
74bb2af3e1 | |||
b20c4047d4 | |||
4e2d3ceaaf | |||
82cf6caba4 | |||
bc3f820227 | |||
12d80c2732 | |||
6c0ce95d0f | |||
750502c870 | |||
df63490266 | |||
c9c6bd4836 | |||
d90420ac4c | |||
7c8504ea24 | |||
94687a7603 | |||
e1be8f61fc | |||
3d252a9797 | |||
45683a53c9 | |||
c4c4d82bf4 | |||
4ed79614ac | |||
73f6a57b12 | |||
260ff99710 | |||
fcc1cd3d57 | |||
08014c6a98 | |||
5da2ab1b7d | |||
d0be193307 | |||
b3fb106cce | |||
66cedf0b3a | |||
707a4ebc15 | |||
46d2efca13 | |||
d95375d494 | |||
1c1c58e802 | |||
7fe05b8296 | |||
17ef531905 | |||
24cd1b591c | |||
bb9e6731ea | |||
5dd5a89775 | |||
b8e2bdd6b1 | |||
88817a8f10 | |||
3e8ce43dcb | |||
9d8845d7ad | |||
2f91aca897 | |||
35c3622405 | |||
52578ba483 | |||
991a4801b1 | |||
02b2c55146 | |||
0abe753003 | |||
487fafbca3 | |||
188a352c6f | |||
e11b400a75 | |||
6db5692be4 | |||
8ab7b27d4f | |||
ead4029d49 | |||
9e76fb2231 | |||
9c7d2ab8f2 | |||
9bd408449e | |||
739425431a | |||
dda6554990 | |||
2f43cc353b | |||
2b7390c2a1 | |||
ab961a78cb | |||
65c639cf13 | |||
0cf5dc11e3 | |||
ceea7e5aeb | |||
579814895d | |||
b873fa7a5f | |||
ee563ecf4e | |||
183b35d683 | |||
463dd48180 | |||
1bd3fdd912 | |||
7655b070df | |||
1355a5dd33 | |||
f62e3119c4 | |||
828585a312 | |||
ef4af443a5 | |||
1a3e1e0959 | |||
40004e64a6 | |||
50dc0ad207 | |||
3da4f02ffa | |||
8a2bba4efb | |||
1ba80224ad | |||
bf19918e3c | |||
38fef28c84 | |||
273f964293 | |||
d2577acccd | |||
d92e661253 | |||
de71cbdd43 | |||
c9b87c4c03 | |||
38848082ae | |||
b6728efcd4 | |||
cd814851da | |||
6646daab45 | |||
ba483155d7 | |||
63abe1cb3e | |||
28db8022fe | |||
7dcc08985c | |||
55acdaaf8c | |||
575c07c9c4 | |||
325f45fa66 | |||
bc682066d8 | |||
0a1cdc5107 | |||
6984185e61 | |||
5826126284 | |||
cb11f042ab | |||
b82a4869d5 | |||
c2be740ad4 | |||
61258d03ad | |||
79a05d63c8 | |||
762e528ec5 | |||
c3de9848b4 | |||
370ae8c20c | |||
18752672d0 | |||
69083bfca0 | |||
cdc37bb142 | |||
083dcd4541 | |||
b6f00d07e8 | |||
b0ffaf1c91 | |||
2af61bd07e | |||
1caae90c02 | |||
184125a70a | |||
7cac5bb633 | |||
53314cb8b2 | |||
b5e287e065 | |||
1e15f26e98 | |||
23ba01d89c | |||
2eeceae613 | |||
653cbe651f | |||
f3e487e829 | |||
9c016ad479 | |||
e602647d4d | |||
9696e4d315 | |||
7f7af2bbaa | |||
b190051e15 | |||
83b28cad8d | |||
ea42a84a4a | |||
e4c282f0a6 | |||
d54d7cc431 | |||
111477aa74 | |||
226739d13f | |||
f1ee9113ac | |||
9120a64cfb | |||
fcd624a722 | |||
e6af7f75a1 | |||
27f1e7b60c | |||
2e24de7f47 | |||
e514204db0 | |||
a8366ebf15 | |||
ad48387aa0 | |||
a4bcc1ff3d | |||
fca3a6b75e | |||
6fcdc76059 | |||
0f9e55dac6 | |||
5d7677dd07 | |||
57073cc6cf | |||
f9f39c0a1c | |||
3eefa6dec8 | |||
8c6feb7e80 | |||
37f8ff0efc | |||
d88d7f26e4 | |||
aeaedd2e5e | |||
1f4ef3b606 | |||
9b5db297a6 | |||
07c22c7e81 | |||
1ac0c0bfc5 | |||
c25209eb34 | |||
b7215b5dde | |||
411435d68f | |||
f656f906ff | |||
d0a7363e64 | |||
7401fa2fa5 | |||
4deed7c836 | |||
92f72b4103 | |||
30f54626d3 | |||
3a8206d1fb | |||
6b0b8744c1 | |||
0b8352049c | |||
c03f700662 | |||
d08f2e73d0 | |||
aa7f23e1e1 | |||
4249c5b3e0 | |||
6f1a5c8e02 | |||
03a93bd089 | |||
6aef00ecff | |||
949c6a5932 | |||
7922bb4020 | |||
697bf16f26 | |||
181ee1dade | |||
3645a0f0e4 | |||
2864eaebae | |||
37612345f2 | |||
bb218b824e | |||
279329bfaa | |||
71f4ea9d76 | |||
1881a297c9 | |||
56c7a99eb4 | |||
3262ffc1a6 | |||
5bc7a1f435 | |||
11cb5ed10e | |||
9916f35b22 | |||
0a6f62bc0e | |||
bc974a3e7d | |||
1aa70c50aa | |||
134b45dc03 | |||
2b80f40164 | |||
70215fe480 | |||
96c0b933d9 | |||
7b51c5c49f | |||
eac02b55f6 | |||
5d4ae4a2a4 | |||
04cbef3aa8 | |||
e540f0ad26 | |||
69fa040361 | |||
720217a5e4 | |||
1911aad57f | |||
1943071d12 | |||
08c624576c | |||
a99a2ce7e8 | |||
56855f9791 | |||
b32979bc84 | |||
651d425046 | |||
d1df9b9e38 | |||
bf1a23afcf | |||
04a6a4f860 | |||
f603b7ef8b | |||
b8c3a10e5b | |||
cab181832f | |||
af2b2c668d | |||
c94c87eec0 | |||
666bee61f7 | |||
a6e0f0bb74 | |||
03ce896f6f | |||
80e0cd4e00 | |||
049477a9bd | |||
d644a8d41f | |||
e0c2074ed5 | |||
d8bf48e692 | |||
a91efc3cbd | |||
fb42c94b79 | |||
ba2e3d94eb | |||
4ef65f0983 | |||
2675ad9304 | |||
c1240f214c | |||
7f3eab418f | |||
4e13c339ec | |||
9a1e1d5b1e | |||
4f89ed5d66 | |||
17008bb648 | |||
43fd0b6ae9 | |||
bb5ab5d16c | |||
e3abadd686 | |||
c36d356f4e | |||
e8c06b7152 | |||
3cc0ea5ff9 | |||
333335c366 | |||
08306f0db8 | |||
3d2e227f11 | |||
29d2449fb3 | |||
008bdfa43f | |||
1d0483c946 | |||
7cb9fddc11 | |||
989062d2f8 | |||
c2f78aaf88 | |||
8f39f4580a | |||
6202c67fe8 | |||
0c82c1920e | |||
a2dc4199d0 | |||
b1970f79ee | |||
6cdd8a2b07 | |||
4ed615cfcc | |||
91da4e3168 | |||
596062ccab | |||
93b5f3f421 | |||
cac2875c96 | |||
a3f119e0bd | |||
6ba40773bb | |||
19c79f0a73 | |||
e58faeb66a | |||
3a3d80826c | |||
49c93e5b9e | |||
3f2a99a936 | |||
62eae9b470 | |||
a1aae8ca38 | |||
104cf5b51b | |||
4fe9d8a007 | |||
edbc828fc3 | |||
03c9eaf005 | |||
2b021472d6 | |||
55cab9eb4f | |||
b39dda0550 | |||
7c0a52a81e | |||
318d13ed58 | |||
21a3ceee92 | |||
a8f6a13239 | |||
b9f1371994 | |||
51c685aa99 | |||
9e39284de9 | |||
26899bc0f0 | |||
a74d05061d | |||
4140834e4c | |||
bd44bcee32 | |||
1e4678f929 | |||
fe5055cf29 | |||
d9d956e54f | |||
6c2c16a971 | |||
955a5ed8fb | |||
a59414203f | |||
631b067281 | |||
02bac0a326 | |||
7c8fb060f1 | |||
04c0e94349 | |||
2a946af81e | |||
18be6768c9 | |||
fbe61a06f6 | |||
7a4d6d64fd | |||
0eae9c49b0 | |||
d0bca1fb0f | |||
7c7e5112ea | |||
ec96e85d04 | |||
d60d71a697 | |||
6f0dd8e885 | |||
de99e35106 | |||
774be79321 | |||
721f704260 | |||
2846e3f5d9 | |||
b9ca3b2039 | |||
8ac572ed27 | |||
c4163c3621 | |||
1d7c909080 | |||
500683831c | |||
9a2fe7ec0c | |||
3ae3e3d23d | |||
fc07e849fe | |||
fadcdde7f8 | |||
d056bf070f | |||
2591050fbe | |||
e8dfd4ba39 | |||
383e874166 | |||
e8a2250ef8 | |||
440e12abc4 | |||
25ba6ea459 | |||
5ec226a416 | |||
a021b99614 | |||
e9de4c08b0 | |||
2e968d2557 | |||
bc6fa85a4b | |||
4e6c2c0fa1 | |||
7eadbd938d | |||
31a5de973d | |||
94fc8a1334 | |||
aa1cd7eba6 | |||
16faafb7a8 | |||
e376c2d0d4 | |||
c4dc61425d | |||
128f5bce30 | |||
82d69305b6 | |||
57a009b8e6 | |||
a2e6f5ebdb | |||
995dbd25b3 | |||
1d0d0425d4 | |||
51890baace | |||
4bca36f479 | |||
7d78f40bf6 | |||
131b5b56d7 | |||
fcd94efbd6 | |||
13257004bc | |||
8b193db0cb | |||
5537dce3cc | |||
fd5da62c66 | |||
927578a26f | |||
290c712cde | |||
2486492c4d | |||
7bf10b980c | |||
55c243a17b | |||
df526f73be | |||
29a77fd6ae | |||
be9ebd9e18 | |||
01f1208ad1 | |||
9dbb3e80fe | |||
a5c14ba7d4 | |||
4b11b283ac | |||
6fdfc84904 | |||
bff81f24aa | |||
ed515cbc0c | |||
87a6d7166c | |||
55baee9a9a | |||
0886afe650 | |||
872f6166e1 | |||
fe348e236f | |||
e0f083d117 | |||
48171f8e24 | |||
bcdf74562b | |||
3a5ee1aed0 | |||
d8c4b9c4fb | |||
6ae7884786 | |||
41834d16d6 | |||
1ee51f2afa | |||
65ee7aa372 | |||
ac38ee82f4 | |||
5fcc7f2328 | |||
3bcc2aad80 | |||
6165b6ae77 | |||
e335e4fddc | |||
5ab4199d71 | |||
f075e2459d | |||
94a26abf21 | |||
bcbdc33049 | |||
21ef3895b3 | |||
3e99dc01b0 | |||
3e325a1974 | |||
2b92e3e8a7 | |||
cb90b90cbf | |||
9776a252ee | |||
751de20f93 | |||
28388b4e3a | |||
4fdbf30308 | |||
722f191e82 | |||
20f6114617 | |||
3075e2cfbf | |||
e2973d2176 | |||
0ff08bb63a | |||
08c0bf52bc | |||
d0229cb96e | |||
0612e5ccfb | |||
1b4f7b34c8 | |||
86e6fcd309 | |||
dc9cd7d8b9 | |||
c0cc9ce7cd | |||
be2f66397b | |||
07760b4129 | |||
d79a3130b8 | |||
440a589f9e | |||
f5fcf9d635 | |||
9b8b1bad57 | |||
874ecd6c88 | |||
57e2fec497 | |||
0905a2c3a2 | |||
3aa00b78f9 | |||
6769d46dbb | |||
efac712f62 | |||
2bb23c57df | |||
bc699a2cc1 | |||
758c128147 | |||
3795c2a39d | |||
311c0e3f50 | |||
25a8caa9b0 | |||
c80a9585b0 | |||
e73491441a | |||
b93b80ccaa | |||
48128c9db6 | |||
6dafaa197d | |||
1634d8e087 | |||
7a583083b8 | |||
75156ab0c9 | |||
9fd6923821 | |||
91a929b2a9 | |||
0f8e31af06 | |||
bd71c2f34d | |||
001123dbd6 | |||
cfee151d4e | |||
fc59291191 | |||
4fc05cac56 | |||
cc4616f25b | |||
e82fbb7bcf | |||
8cd639f6a2 | |||
a8f555856a | |||
3792562046 | |||
d05c48a1d7 | |||
36cc5eb933 | |||
f9f74a0f7d | |||
77f42931ff | |||
73f62266c6 | |||
df2f3d25b0 | |||
599c43ce04 | |||
5c2199e7f4 | |||
3ad4e0348f | |||
02d5729941 | |||
ce35689d2e | |||
da81e21bf2 | |||
3b2ed7631f | |||
1a46e70dfb | |||
0fc9b6cfa2 | |||
61768aa2fd | |||
ea5bf9db36 | |||
9d24afcfe3 | |||
033df9457b | |||
d8e105fe34 | |||
d34068da18 | |||
611103d211 | |||
528c1c5fd8 | |||
f73732bf1e | |||
fd7875e572 | |||
e8bc319f08 | |||
a92ff57270 | |||
004230d02d | |||
a148c640b2 | |||
ea0205f2ff | |||
005649b6fc | |||
e09e3b01d6 | |||
fc15e0e27d | |||
b2fe5fabb1 | |||
52d69bb021 | |||
5f550a355b | |||
dbecbdccd4 | |||
734877338d | |||
2e439ca77f | |||
a853880e07 | |||
93f3ed98e1 | |||
a131eddf54 | |||
b19a7aa8a6 | |||
f5aa53c530 | |||
80f5e14512 | |||
41390cd963 | |||
0b5e131410 | |||
556596bce8 | |||
b791d1ab0d | |||
ac070ae942 | |||
111ad868a7 | |||
a7274115d0 | |||
81160bcefb | |||
2880109f31 | |||
09a1f5acb9 | |||
5fcf11fcb0 | |||
42fac722bb | |||
073e5727c6 | |||
ad1c4f5e39 | |||
dc8a68c98f | |||
e5621dea58 | |||
00acf22f5f | |||
4c09716ad8 | |||
1c941557c3 | |||
28e1a7915d | |||
4bc9d9fd3b | |||
e278ca61d1 | |||
2146ede15d | |||
e737222a5d | |||
f03f1949bf | |||
0fe6c7c558 | |||
b13202bbfc | |||
90fae903ce | |||
06b154f4b2 | |||
419a0665c8 | |||
387098fc87 | |||
c42b588782 | |||
4faaa5310e | |||
c448abd44e | |||
2517588d7d | |||
8fc8fc89aa | |||
b243b3ee1d | |||
28e08afada | |||
7e184b58b2 | |||
589fc0b8ad | |||
f0c7c1b500 | |||
840bd98e01 | |||
a5cdd22bfe | |||
0c7bcae9b1 | |||
ab666c170c | |||
d2213d18fa | |||
82b6300dcb | |||
b69cda9e07 | |||
56adc7c3c6 | |||
2ace20fade | |||
c13fe83784 | |||
6cf8df8685 | |||
86a89404be | |||
0d305d7c3e | |||
ee5bd2b4b3 | |||
22ae962b57 | |||
864139d67f | |||
1dc7e00d20 | |||
49a9107e0f | |||
7b8c2c232f | |||
15e1e6376b | |||
06d9d9ed08 | |||
74e10d6f72 | |||
d43489a6a0 | |||
983de8974b | |||
c91a1ec08d | |||
507de45d40 | |||
fe0fc8d5e1 | |||
e4a8db56f9 | |||
1d1ec4727a | |||
0b71e45072 | |||
9c375b33a6 | |||
28a6a5ea57 | |||
f83ff0e47d | |||
079e575cac | |||
6b2327f231 | |||
596608aa0c | |||
120e80d1b6 | |||
aa6c6120f6 | |||
19d5f782cc | |||
84169a91ff | |||
d1c48cdcf9 | |||
dfe95d3ae6 | |||
57ebec385f | |||
7a77910720 | |||
23d8dc959c | |||
7f303a856e | |||
e834e617f3 | |||
2c89a228d5 | |||
803826cdcd | |||
42d18d2294 | |||
b5ae024cc8 | |||
5968811441 | |||
fc59c87606 | |||
7dc1d6a350 | |||
deff1aa63b | |||
08e7d0dfb6 | |||
892aae267d | |||
11a9144e84 | |||
039f223b53 | |||
e1cb026184 | |||
2a96152a43 | |||
0795d56c1c | |||
48a90fea70 | |||
b202951c1d | |||
c3d2c61729 | |||
d7b707939f | |||
991ac6eb77 | |||
011b7c4a07 | |||
617341f8d5 | |||
abd2632977 | |||
5481db4079 | |||
041086d22a | |||
aa564f5072 | |||
8367f2001c | |||
1cfb228924 | |||
b403fb1275 | |||
3443ca40c5 | |||
96f95653a6 | |||
7f7e8465da | |||
e3a273cf73 | |||
233161d56e | |||
d883ab250a | |||
ef4e3f907c | |||
debeadbf3f | |||
d66baaceb9 | |||
85329f9a81 | |||
a5fefaf78b | |||
6f17662a4e | |||
c83aea3c89 | |||
67aaf4cb2d | |||
3083346884 | |||
d07789677f | |||
fb1846120d | |||
da1e1295ea | |||
ecaea57263 | |||
fa928bd25d | |||
c1981dfc26 | |||
fd41fa31d5 | |||
2c52144f41 | |||
87c7898b65 | |||
44e088c6fe | |||
7b4cbd7ce9 | |||
b052d524da | |||
47c4b8e88a | |||
d0a2a02eea | |||
b1e1dab4cb | |||
388973e9ab | |||
2129ec7558 | |||
82f122525c | |||
7c4c00f1e6 | |||
fe6c7dc10a | |||
9bc24e3b12 | |||
833baca66e | |||
9fd92512a2 | |||
b692ca7896 | |||
52dc04a35a | |||
42b1287759 | |||
5a471aa1d0 | |||
a4b8d4a098 | |||
a3be6affa4 | |||
71b99edd48 | |||
64553ddcb7 | |||
2a42482ae9 | |||
11f345a8ae | |||
fec50d8cfe | |||
05e42381df | |||
b435075e09 | |||
430da53f0b | |||
2e6d836dd1 | |||
899d324a9c | |||
576ed6a906 | |||
d744cf8437 | |||
088e662285 | |||
f9b0b81eb2 | |||
c5485c6501 | |||
d8ed01400f | |||
ebc4694e05 | |||
a9441d670e | |||
495d2ebd70 | |||
ad26adc3e3 | |||
63a62e19f9 | |||
4f2ae34df9 | |||
a636f161a4 | |||
dfb1e22559 | |||
dff85a7f70 | |||
3be198d2f5 | |||
d19314fe3a | |||
d06f457b2a | |||
7d07881d96 | |||
3e6e3a207c | |||
481c6d4511 | |||
231a445809 | |||
93e8f6c05e | |||
9de2144fc4 | |||
363dc51ba0 | |||
99117ff2ef | |||
5356cb9fbd | |||
0e13d9fbaa | |||
2dcb16870b | |||
ac9909112f | |||
eb3c2c9e76 | |||
3d29e3efbf | |||
f410fb6689 | |||
eb62fd466e | |||
b50cdd6de8 | |||
f38e2b5c6d | |||
455915ec9e | |||
2333158256 | |||
3ffa804088 | |||
98810d22b1 | |||
5e72b2a797 | |||
7e4e7fa4a6 | |||
d297199d7c | |||
17a433996e | |||
b9bb4692a4 | |||
d05dcdda02 | |||
27fe356214 | |||
fc44df1e45 | |||
77f915befe | |||
a5f7600f6f | |||
7eb8634ad7 | |||
452d8c06e9 | |||
48f535f02e | |||
43c10b0625 | |||
328b09fe04 | |||
15d49e4096 | |||
3ef53fe2cd | |||
7d8e759e98 | |||
69b3be61a4 | |||
79476a5cb2 | |||
f449baf8de | |||
5ff4bcfb7a | |||
98537ce8b7 | |||
d2a00a2daa | |||
f22938fc4a | |||
1e67ae8e94 | |||
c012d648fb | |||
67acaae53c | |||
e3da546e23 | |||
e5b136f70d | |||
058ef69da3 | |||
2a483531a4 | |||
05202671db | |||
8509873043 | |||
57a2d695e2 | |||
0b5ab1ef22 | |||
2eac79569c | |||
ac578b8491 | |||
5183fd25bb | |||
10f5a8ef78 | |||
a30837298d | |||
f377a3a7b4 | |||
83c874666a | |||
e000ed47cd | |||
af2f064f42 | |||
9c7b25134b | |||
2d15df9e6c | |||
d2ab287756 | |||
e73278990c | |||
12bc92df35 | |||
f19a801022 | |||
b193303aa3 | |||
e299e76fcf | |||
c857e18c4a | |||
5fb3df4054 | |||
8b597187fc | |||
930f9f0063 | |||
63d4df9810 | |||
13ba533fc4 | |||
6d60bab2fd | |||
5be774b2e5 | |||
b412ff92c0 | |||
5a75e11b0e | |||
e66bf70589 | |||
3924e9d50a | |||
8df748463d | |||
0113661c81 | |||
0ee054b14d | |||
80b39454ff | |||
97f3671e2c | |||
b674cee9d2 | |||
cb8491cfee | |||
8196b031f8 | |||
50dd56d3c4 | |||
0f7e1d4d01 | |||
ec77c572b9 | |||
f97561c416 | |||
5faa82e323 | |||
4e17292a12 | |||
666fbbb0d1 | |||
c6fe58467b | |||
46d1938f5c | |||
8229af7591 | |||
ee76523507 | |||
c283db373b | |||
1b0ed30516 | |||
a6fdee4a51 | |||
6951fb440c | |||
502c9ea706 | |||
22f67be461 | |||
77ffd06715 | |||
1d833ef972 | |||
0d8064ed2d | |||
cc06ea4d87 | |||
3cf7652e86 | |||
1eb28c6cb6 | |||
db590369a8 | |||
f4d654d2a2 | |||
5725e55abb | |||
b6d19cc9fa | |||
bc6c884a14 | |||
cb78bf8fd6 | |||
400bc97e35 | |||
2fd464bf7b | |||
e626522b3a | |||
791e07650d | |||
bf2363947b | |||
a2cc2259e7 | |||
808fe496a6 | |||
2fb48bd6ac | |||
2df8775b48 | |||
e02b4f1443 | |||
194782215f | |||
df17d28c0f | |||
5f43c8f024 | |||
1a18734f9a | |||
4a70c1ff4f | |||
770e5d89f2 | |||
cfac8e84dd | |||
43d90c1745 | |||
38bdb053d2 | |||
95e61773a5 | |||
4e931fa73f | |||
2573441e28 | |||
5770b15270 | |||
6817b472d0 | |||
2b076369e0 | |||
973a8ee8f3 | |||
1159d3365a | |||
152ba32eb7 | |||
153320ef33 | |||
ff236da72c | |||
54326869e4 | |||
f14f4e39c5 | |||
a18b2702ca | |||
93410c470e | |||
5d945ef869 | |||
df07be6a42 | |||
3c32d4947c | |||
2ea5235aea | |||
c096f031ce | |||
ae1d4bdb4c | |||
0adf2accdd | |||
4201f48be5 | |||
b076e375ca | |||
2f1016d44f | |||
ddf9d61346 | |||
f0b7ab5ecc | |||
e4c6336bd4 | |||
66061192f8 | |||
b7bc4c1f80 | |||
a56abb6502 | |||
892a416211 | |||
f45adecd01 | |||
bd015e82dc | |||
cf43b74f26 | |||
18909ec14a | |||
ed243c88d2 | |||
cb7723f423 | |||
9dc88f8a95 | |||
0a439fe52f | |||
a8b65e35ec | |||
bd9e598bf0 | |||
75f8247af1 | |||
e8ec5027ff | |||
ebba89ea31 | |||
8388afc9d9 | |||
b133724b38 | |||
09429d08aa | |||
9b577b8679 | |||
7a595827f1 | |||
332e12ded0 | |||
a508e15efe | |||
a5b6bb6209 | |||
1882a32b83 | |||
798766b4b5 | |||
193c4cc6d5 | |||
422b6ca871 | |||
2b13ac3856 | |||
4c10351579 | |||
dd27aaef1b | |||
6eb4a0e87b | |||
15f3a545f0 | |||
365f76ad19 | |||
df2845a9b4 | |||
8453261211 | |||
1dc8f3300e | |||
10d4edc7af | |||
50cbf91bc5 | |||
d05f9b3b1e | |||
f5fad393d0 | |||
d19a5f4c2f | |||
04451af776 | |||
232aca76a4 | |||
0178b53289 | |||
e05e6b42fe | |||
dd79afb503 | |||
599bb9797d | |||
c355585112 | |||
45f32c9541 | |||
7528094e12 | |||
dcfa135ab9 | |||
e9bb4f25eb | |||
0f7a9bbd31 | |||
73e65df5f6 | |||
a63a5adafa | |||
2eb4f8d28a | |||
d9ae66791a | |||
2c5939dc7d | |||
3150e70fc7 | |||
c9ffd6afc0 | |||
986b427038 | |||
c973850571 | |||
5a725f9651 | |||
79cc725aff | |||
e2cbc4e853 | |||
b5a27f0ccb | |||
bdb12f4bff | |||
c9c29f9e4c | |||
32951f1161 | |||
56f85b3108 | |||
16f85f32a2 | |||
2ae2f2ea9d | |||
4696c9069b | |||
1ffbb66e64 | |||
8dc7b8a7cd | |||
666e6a7b57 | |||
47c5346934 | |||
882cf74137 | |||
57a26bbd42 | |||
f9acb7a7a5 | |||
569345e1d4 | |||
adbbcafd30 | |||
860c2a606d | |||
6b5d96337e | |||
f54cf8a096 | |||
b5d591bb09 | |||
60ce497edc | |||
dd4351e2b7 | |||
3f443f40d0 | |||
8192360b20 | |||
889b67ca92 | |||
8d1cecf643 | |||
188d33b306 | |||
965e07d8cc | |||
d6b6b1df38 | |||
c897ac6e1e | |||
df691c6c91 | |||
abc05ece21 | |||
6f69ae8707 | |||
84a6010f71 | |||
634bb688c1 | |||
c14b209276 | |||
6535ae3d6e | |||
0390ec97f4 | |||
e3c4d82798 | |||
ee71a35786 | |||
11ea5e61fc | |||
02763b47f7 | |||
4828a7cd29 | |||
728852c750 | |||
26cec83b63 | |||
4724b3c570 | |||
303a9defd3 | |||
a64270829e | |||
8f5df89a78 | |||
39f402c8cc | |||
7702d683c7 | |||
766533aafb | |||
781e423a97 | |||
6e3a827e32 | |||
6685f74e03 | |||
034c33c2b5 | |||
08d1be79fc | |||
c563b7862e | |||
a64cfb6285 | |||
f078aacc25 | |||
d859bff877 | |||
de5cd4ec23 | |||
48850becd8 | |||
2ea42f296c | |||
eb2ba470c7 | |||
a951edd0d5 | |||
d65a38dd41 | |||
8f568f4fc5 | |||
ee26590011 | |||
11352f87f0 | |||
9f85b10fcb | |||
0dd1403a69 | |||
cb4527fc0d | |||
ad395944ef | |||
6126209f57 | |||
43e061f8c6 | |||
738541f727 | |||
1d5518a214 | |||
57101d5022 | |||
f6ff6ab6e4 | |||
a224cd38ab | |||
e292bb46bb | |||
05aca1c157 | |||
8d269f62dd | |||
b1a946f0dc | |||
c59f860b48 | |||
c6588c661a | |||
8fe269a3d8 | |||
8f00713ad2 | |||
d1d98a897a | |||
3dc95ef765 | |||
84da815b22 | |||
371a951668 | |||
baf84f05d9 | |||
da4d24d082 | |||
22519c9083 | |||
1601aa2f49 | |||
88555860f3 | |||
015d2ee050 | |||
dd7ee1808a | |||
0db4180cea | |||
8ff15c46c1 | |||
48cfc9b598 | |||
87d71604ad | |||
e372e7c448 | |||
0194dee3a6 | |||
cc3c10867c | |||
3c18169f63 | |||
43e9c89125 | |||
2ad07912d9 | |||
51ad019495 | |||
9264325e57 | |||
901157341b | |||
eb766b80c1 | |||
f0dbffd761 | |||
f14c0df582 | |||
362bb1bea3 | |||
724b177c97 | |||
50343f2d6a | |||
3122525b96 | |||
8232c6f185 | |||
6202705eb6 | |||
e1c5940b04 | |||
7f35bfc005 | |||
c48c092125 | |||
028fc9b9cd | |||
eeb9b4edcb | |||
3a7869b422 | |||
c48ea46c4f | |||
f33da33626 | |||
a88f5c7ae7 | |||
cda53b6cda | |||
ee734873ba | |||
9fb6f5cd09 | |||
4ef15b5f80 | |||
ba81278ffd | |||
10fbed3808 | |||
16cfc36aec | |||
aca7f71737 | |||
3282a509a9 | |||
878b748a41 | |||
18a4505b9b | |||
26e77a4b05 | |||
37f10cf273 | |||
5e0a9aecaa | |||
7e2c627044 | |||
4347339e9a | |||
e66a8258ec | |||
e4b42b54ad | |||
de18b9ca2c | |||
a77f0f7b41 | |||
6b31a006b8 | |||
2db4fe83d8 | |||
55a2f284d9 | |||
2d3b1e090a | |||
ed0c1038e3 | |||
0c20282200 | |||
e71f44d26f | |||
e3d7e46855 | |||
9b35aae5e8 | |||
7e9f87c57f | |||
5d17b72852 | |||
6b4634b293 | |||
2a084fc838 | |||
a36d2a1586 | |||
32b875ada9 | |||
aaed9c4e8a | |||
b9278bdfe1 | |||
6eb2c94209 | |||
7b1a15b223 | |||
836efd237c | |||
aad3cca793 | |||
6829ad7a30 | |||
1f0962eb08 | |||
c65acc174d | |||
2dea392e40 | |||
0c43a4d04b | |||
ebc2d40875 | |||
3432078e77 | |||
9e5170b3dc | |||
0ae7c5d836 | |||
d0712a00f4 | |||
5e722181cb | |||
ffe3e2c16b | |||
04e8aa31fe | |||
9d24b440bb | |||
d8594a62c2 | |||
dbe0effd67 | |||
b358804904 | |||
7b02604e6d | |||
6497421615 | |||
f26151e36d | |||
0f688d7da7 | |||
a04dfca63a | |||
72f6513d2a | |||
7c0a830d84 | |||
c299d207f7 | |||
42a1adf2e9 | |||
b4761f9d8a | |||
71e55541d7 | |||
5f1075544c | |||
0934410b38 | |||
17e6c53b62 | |||
80d2a7ee7a | |||
8fd22b61be | |||
e9313a61af | |||
f2c4d22739 | |||
8551e06d9e | |||
97cedeb324 | |||
07594222c0 | |||
7a207a673b | |||
78f13407e6 | |||
5a34744d8c | |||
0456f4a007 | |||
f3f40df4dd | |||
bdef5d7d72 | |||
8d03cf5b02 | |||
3ec0242960 | |||
0bc2e29f99 | |||
1bb6a2d9ed | |||
e848fc0bbe | |||
6820d70e7d | |||
f32ab696d3 | |||
e07a9e4ee7 | |||
6a89b1b010 | |||
b1b93931cb | |||
1e62a8fb6e | |||
ed6f337a48 | |||
b004236927 | |||
0fdb9ac5e2 | |||
28be39494c | |||
32f18536e1 | |||
34e1e6e426 | |||
c3ba1e476f | |||
a1a0710ee6 | |||
455b1ac294 | |||
b2e0dc5b77 | |||
d30c40b40e | |||
85d848dd7d | |||
74717582ac | |||
ee18f16378 | |||
9e82e5a2fa | |||
8ea2307815 | |||
bbc5a28fe9 | |||
04120e00e4 | |||
efd8a633f2 | |||
e75c44c95b | |||
0629c896eb | |||
eb02c773d0 | |||
e31e8d1550 | |||
8775991c2d | |||
de8e2841a0 | |||
5cafead4a4 | |||
180290f3a8 | |||
7813063c93 | |||
ba5d774fe1 | |||
7be49e43fd | |||
dcd2227201 | |||
2dd28c2909 | |||
0522023d4c | |||
9876169f5d | |||
ed10aafa6f | |||
bcddeb3c1f | |||
3f170c7fb8 | |||
8d91d151bf | |||
821d44af54 | |||
a30901ff7d | |||
94a1968a88 | |||
dffc9c9b1c | |||
8b3964f518 | |||
7fed9992c9 | |||
4e2a4236f8 | |||
05781607f4 | |||
6daec399e6 | |||
306dc89ede | |||
80ce8acf57 | |||
8dfc90a322 | |||
ad5e485594 | |||
60ed40f8bd | |||
a6228cab9e | |||
1857ac69d1 | |||
e33e80ab24 | |||
d18bc78e7c | |||
3b2a87b6d4 | |||
62c76be7ca | |||
733f93e673 | |||
2c88b2fae7 | |||
501da433d4 | |||
0e8a239ae1 | |||
bb08a221e2 | |||
0dbe347f84 | |||
72a21ad619 | |||
6372d2a18c | |||
4468947ad4 | |||
93144a0132 | |||
72f7406057 | |||
c56cbd0f6b | |||
1420cbafe4 | |||
053bd926ec | |||
d095cb91e4 | |||
e8476d8fbb | |||
7532618bdc | |||
e3e1e6f81b | |||
bce6f5a3e6 | |||
480600c465 | |||
89c737f456 | |||
4e83363dd3 | |||
de6d8738c4 | |||
853d7e7120 | |||
b0c30098e4 | |||
fcbaefed52 | |||
77e02ac1c1 | |||
088901b24f | |||
ed7a62bca3 | |||
6bfd8532e4 | |||
bc9cc75c8a | |||
53a6e9f0bd | |||
5f9de80d9b | |||
353b33be1b | |||
96d58094cf | |||
94aac0e8dd | |||
9f54d238ba | |||
778e497903 | |||
6914099e28 | |||
1b6f94b46c | |||
3d63901b3b | |||
eb1ada6115 | |||
831111edec | |||
29ea29261d | |||
ee835f75db | |||
bd7ac0d48e | |||
d7b1480ad0 | |||
86b316e930 | |||
a042f407c1 | |||
40673e4599 | |||
bcfb084d4c | |||
a1fd5ad128 | |||
fe6d96e996 | |||
e24e0242d1 | |||
c959dc1ee3 | |||
d82ce26b44 | |||
935a5f6b9e | |||
731aa6bbdd | |||
a268e825aa | |||
982f067d0e | |||
a3e1a3f266 | |||
e5a18eb3c2 | |||
16ba274170 | |||
3bb2c9beed | |||
2fa83b0bbe | |||
bf459e09cb | |||
ec7ff5960d | |||
545f70705e | |||
48672f8e30 | |||
160191e9f4 | |||
bef9669b85 | |||
15e66ae065 | |||
ba6370621f | |||
2a8ea88413 | |||
05959d6a61 | |||
012c99839c | |||
5dd346094e | |||
b6f9d0ca58 | |||
ae72593831 | |||
ac22319a5d | |||
7e8c84e394 | |||
ef4eefa96a | |||
2dc43775e3 | |||
4bdf27b173 | |||
741d7b9f10 | |||
ecb67fee40 | |||
ad43ef08e5 | |||
092ee127ee | |||
b84ff99e7f | |||
3a6a3d7409 | |||
48ee20782f | |||
360e8340d1 | |||
fbc0dd57e9 | |||
3f9871f60d | |||
fe01a223a4 | |||
0a6692ac44 | |||
98a3d9fff6 | |||
e2dabecc0b | |||
49b0592e3c | |||
fa812849b8 | |||
9567c1f564 | |||
a915471b38 | |||
bf212a5a3a | |||
f0fc9e1038 | |||
cb6ccc3c5a | |||
07996ea93d | |||
c71510c65c | |||
9c9941cf97 | |||
005d76cf57 | |||
8a99d112fc | |||
fb09d7d1a1 | |||
9c14fb6c02 | |||
d488fddfe1 | |||
e1b598d478 | |||
edbecda14d | |||
74c2daf665 | |||
aadbcf5ce8 | |||
460daf029b | |||
9e6ab33fd7 | |||
5de30d0ae5 | |||
97b9c078b1 | |||
8dc5c34932 | |||
3239e5055c | |||
b22db39775 | |||
7c61f427c6 | |||
ae8c864934 | |||
ed80933806 | |||
ae87582cb6 | |||
b89976daef | |||
76b170cea0 | |||
3302586379 | |||
3144dc7f93 | |||
6efabef8d3 | |||
0743b69ad5 | |||
5f1136dcb0 | |||
acf13a6fcf | |||
3fc4a9f142 | |||
1d781e8797 | |||
b6cdfb1b19 | |||
334685af23 | |||
c475be2df8 | |||
6ec6eb5199 | |||
f18424a6f6 | |||
d1b1438ce5 | |||
af6aff8ca3 | |||
d4dd8284a6 | |||
e728cecb4d | |||
41e1aef369 | |||
e50db1a4de | |||
41412bc577 | |||
e12aa87abe | |||
0abc94f0c6 | |||
48d06f40b1 | |||
f43ed23ed7 | |||
40ec8c41a0 | |||
076fde16dd | |||
822440d5ff | |||
fc8ee8e4b9 | |||
5fbe5cf785 | |||
f78536333a | |||
e7f08cb21d | |||
f5a1d2f4fb | |||
8abbfd3ec9 | |||
6826a9aeac | |||
e3b7e47515 | |||
196991ae1e | |||
34b5e5c34e | |||
cb24a9c7ea | |||
9700b74407 | |||
803c6539eb | |||
75edcbc0d0 | |||
b2eecfb110 | |||
b0aa142542 | |||
247d8b00f8 | |||
0b520eeaf0 | |||
c3535b5c67 | |||
8b9a8daa1d | |||
c5ea4a31bd | |||
2275575575 | |||
c3a066eeb4 | |||
42eb658c37 | |||
a2e9bbd358 | |||
8951a01e58 | |||
f702aae72f | |||
f5e03aaf1c | |||
0f0847e45b | |||
ccd5d69fd1 | |||
55374ee54f | |||
f93ff9ec33 | |||
9a94b3c656 | |||
e04b89f747 | |||
180c1204f3 | |||
96e5fc05a3 | |||
c3efdf2689 | |||
27fdef5479 | |||
7ce8026916 | |||
8a9fc6a721 | |||
c06a692709 | |||
b37e420c7c | |||
22e70478a4 | |||
8ab2b92405 | |||
3201c90647 | |||
454f560eaa | |||
d2ac506de3 | |||
a9968046ed | |||
453087248a | |||
81ff598d6c | |||
d7d487de73 | |||
8d69c77989 | |||
0779a46179 | |||
ada92f41b4 | |||
ef3049f5a1 | |||
1dab82ffa1 | |||
e9e3fac59d | |||
7d403a6cc7 | |||
cf53264438 | |||
d834708be8 | |||
f8b4784891 | |||
789b28ac8a | |||
db8219e798 | |||
73d5310c9c | |||
8d197e1b2f | |||
c704157bc8 | |||
6abb9181d5 | |||
006171d669 | |||
8bd3cedce1 | |||
6f2ef05195 | |||
80025ea684 | |||
a62745eefb | |||
2ac501f88e | |||
df90d9e4b6 | |||
ad7a3fd908 | |||
ad8ab5b04d | |||
e7767ab7b3 | |||
846a779516 | |||
e7a4f31b38 | |||
10768b6ecf | |||
716c4def03 | |||
0e510ad42b | |||
3c222916c6 | |||
6887554888 | |||
d7bd77829f | |||
9e8434326d | |||
27bff35c79 | |||
e2fae63a42 | |||
701711eada | |||
9ec2aca86f | |||
818171cc2c | |||
b3c623396f | |||
88f06c81b2 | |||
e6b315f05b | |||
01ef6b0732 | |||
c7e11a5a28 | |||
ce0231049e | |||
0f7b270740 | |||
72cf57dd99 | |||
e4fdb36511 | |||
2ffb14c7d0 | |||
eec94e4016 | |||
6412bfd58d | |||
522a828687 | |||
6b8c6dec0e | |||
2b0212880e | |||
a16a91ede8 | |||
c2a9bc3bf4 | |||
e5a79d09df | |||
7974e09eeb | |||
52d2d2b888 | |||
ee778d2b03 | |||
928188b18e | |||
59d516064c | |||
bd5836e25d | |||
e3da037b80 | |||
08a09e2273 | |||
85d6b24be3 | |||
ed583bd79b | |||
e0fc09ac52 | |||
38b2846024 | |||
57c62de66f | |||
dd4935fb23 | |||
18dd009ca8 | |||
c0dda36217 | |||
75b72f844e | |||
fbddc12c02 | |||
8e7e8c17e1 | |||
8ac9d781fd | |||
c86cf31aac | |||
2c513d1883 | |||
04702530a3 | |||
c9f424977e | |||
183c8407de | |||
d0618b0b32 | |||
c4daa2e40f | |||
0a198b9bd0 | |||
6a604491f5 | |||
791f7dd9c3 | |||
a4c1b092ba | |||
6e71c1008d | |||
906d0b920f | |||
efbf4f48c6 | |||
2ddab3e8ce | |||
35dc7438a5 | |||
2a54ee0c54 | |||
cad2741e9e | |||
ae5f3c8210 | |||
a5e97ca549 | |||
06f87cfbe8 | |||
d4e78c6f47 | |||
3653400ebc | |||
81a48d6d0e | |||
f030ab3f12 | |||
0dc0c6a10a | |||
53c8185af3 | |||
36b5d063c1 | |||
a7ec00a037 | |||
918822ae0d | |||
ab5e24a0e7 | |||
b5ea522f0e | |||
afa963fd50 | |||
1e343ff00c | |||
21a543a901 | |||
390deb4ff7 | |||
1c4cb30d64 | |||
1ec2ec72b5 | |||
0d244a9701 | |||
b36d21e76f | |||
d8c4565413 | |||
22ba4c2a2f | |||
8d19b21b9f | |||
45a3afdc79 | |||
2d078849cb | |||
b6363f3ce1 | |||
5ca9e12b7f | |||
5b0b2f1ddd | |||
3afb53b8ce | |||
b40d16310c | |||
d3718d00db | |||
f716f61fc1 | |||
b2ce669791 | |||
cd155f63e1 | |||
9eaa6877f3 | |||
a6b6afbca9 | |||
62666bebc9 | |||
d1fcce0cd3 | |||
a2443fbe02 | |||
db16b56fe1 | |||
54bf671a50 | |||
755d0e648b | |||
e440d8c939 | |||
01dd358a18 | |||
50fb97f6b7 | |||
ebf139f5e5 | |||
8925ca5da3 | |||
287652573b | |||
db24ad8f36 | |||
f88674f353 | |||
59cb0ba381 | |||
c4cfab5e16 | |||
b2c5af457e | |||
c731a5b628 | |||
f97f9d4af3 | |||
ed7d3fed66 | |||
7304d06c0b | |||
ca615d9389 | |||
6d096206b6 | |||
2a8cb24309 | |||
8d38743e27 | |||
eabfa2de54 | |||
a86a0abb90 | |||
adcda450d5 | |||
147b9d4436 | |||
c43a58d9d6 | |||
e38442782e | |||
b98f893217 | |||
bd6556eee1 | |||
18d988d4c8 | |||
0f7c723672 | |||
afce2fd0f9 | |||
4fd9974204 | |||
71615f77a7 | |||
9bc5022c9c | |||
552848b8b9 | |||
8ae8ebd107 | |||
473e9f9422 | |||
96985aa692 | |||
0961da406d | |||
84927d52b5 | |||
73312b506f | |||
c1bec3b443 | |||
c0be02a434 | |||
2ab8d035e6 | |||
24094acee9 | |||
0b2be52bb5 | |||
6a371802b4 | |||
29ccb9f5cd | |||
20ab125861 | |||
fb532f3f4e | |||
a29d52158e | |||
dc50e61f26 | |||
a2668e3327 | |||
e606407d79 | |||
5f4fae5b06 | |||
3687603799 | |||
643b532537 | |||
ed86b1fbe8 | |||
44a114111e | |||
812a76d588 | |||
e3be849c2a | |||
ba1b67c072 | |||
fa910b95b7 | |||
427bde83f7 | |||
7a0bc6bc46 | |||
c6da56949c | |||
5b398d2ed2 | |||
dcdfa2a866 | |||
9474fa1ea5 | |||
49a1385543 | |||
6427ea2331 | |||
3610baa227 | |||
4e201d20ca | |||
1fa21ff056 | |||
0bbd12e37f | |||
7df8fdfb28 | |||
6a39cd8546 | |||
dc3370b103 | |||
ac5ad45783 | |||
8ef5c47515 | |||
5b19bebe7d | |||
2c529cd849 | |||
407f36af29 | |||
763fcbc137 | |||
7061af712e | |||
9b4ba09c95 | |||
9ec6d0c90e | |||
f20a4a42e8 | |||
caa6830184 | |||
f8be1becf2 | |||
af51a0e6f0 | |||
23d11d5e84 | |||
6da9e2aced | |||
32dfb32741 | |||
d48f99cb0e | |||
35359cbc22 | |||
b52dbcc8ef | |||
4429a75e17 | |||
583f27dc41 | |||
83db5c34c3 | |||
cdbfdf282f | |||
a5e1372bc2 | |||
798a24eda5 | |||
a2bb23d78c | |||
d38a63473b | |||
2b37ae3e81 | |||
bc5a969562 | |||
fe4ad5f77e | |||
07191754bf | |||
66bd331ba9 | |||
762c798670 | |||
3c01526869 | |||
7efb31a4e4 | |||
c8dd7838a8 | |||
3b57ee5dda | |||
fb977ab941 | |||
e059c74a06 | |||
47d987d37f | |||
3abfefc025 | |||
a5c5b4e711 | |||
ba9cb753d5 | |||
ba7a1752db | |||
29431e73c2 | |||
d29fe6f6de | |||
e2e9abab0a | |||
2956b0b087 | |||
b32eceffb3 | |||
3adf52b1c4 | |||
78a644da2b | |||
98028433ad | |||
2ab5803f00 | |||
65980c7beb | |||
29fd8b55fb | |||
2f039b3abc | |||
d3dae05714 | |||
5fd3191d91 | |||
0dcd90cb8f | |||
02d0a4107e | |||
63885c4ee6 | |||
147bfefd7e | |||
60043df917 | |||
6d3a30772d | |||
347f91ab53 | |||
5692a08e7f | |||
515a3b33f8 | |||
c3e466e464 | |||
00c0327031 | |||
7451414b9e | |||
41ebc6b42d | |||
b574dc6365 | |||
4af9e1de41 | |||
77d856fd53 | |||
6dceabf389 | |||
5919c6c433 | |||
339a2de0eb | |||
3e3cb15f3d | |||
5e31851070 | |||
0f626dd076 | |||
aa577bf9bf | |||
25298d35e4 | |||
78016446dc | |||
b304de8199 | |||
72838cc083 | |||
8093612cac | |||
f37f29b441 | |||
dba82ac530 | |||
0615adac94 | |||
21e508009f | |||
a9317d939f | |||
65d843c2a1 | |||
f6c62bf121 | |||
b4bc5fe9af | |||
10368d7060 | |||
68a314b5cb | |||
3c7633ae9f | |||
dba347ad00 | |||
bfba2c57f8 | |||
c69bf9f46f | |||
7ce1ddc6fd | |||
e7ce6f2fcd | |||
0c786bb890 | |||
8d31c32bda | |||
e7fb15be59 | |||
be7550822c | |||
0ce216eec4 | |||
1fe85cb91e | |||
8cadc5a4ac | |||
f9da7f7d58 | |||
367f11a62e | |||
8a45ca9cc3 | |||
e336930fd8 | |||
172ccc910e | |||
a8425daf14 | |||
b629136528 | |||
91ebb7f718 | |||
96484161c0 | |||
d21ddeeae6 | |||
4322d373e6 | |||
08571392e6 | |||
f52235b1c1 | |||
a66147da47 | |||
df778afd1f | |||
d7ddaa376b | |||
2ce892c6f0 | |||
28179ef450 | |||
2c6336c806 | |||
761fc9ae73 | |||
314c3c4a97 | |||
f7f1fba94f | |||
14817ef229 | |||
98233dcec1 | |||
6540509911 | |||
594eae1cbc | |||
5e961815fc | |||
fa9329c8e3 | |||
6c577e18ca | |||
4034129dba | |||
52cf65c19e | |||
cbbb246a6d | |||
87cc6d6f01 | |||
4b9ef5a9d0 | |||
31c703891a | |||
550bda477b | |||
219b7e64cd | |||
98c59f77b2 | |||
e8800fdd0c | |||
09f903c37a | |||
57af9b5040 | |||
16272b1b20 | |||
1dcbd89a89 | |||
eb6ef02ad1 | |||
17586bdfbd | |||
0e98cf3f1e | |||
e2a95c3e1d | |||
5cb7df57fc | |||
88f899d341 | |||
7d70b5feda | |||
fd6ee03391 | |||
9f702fe01a | |||
c9d9eec7f8 | |||
38cbfdb8a9 | |||
f9b7376949 | |||
e98ed1b43d | |||
251c3e103d | |||
d26e938436 | |||
dbadf9499e | |||
28df1559ea | |||
91784218c0 | |||
eeec5e10c3 | |||
0515ed976c | |||
f653992b4a | |||
b5f8c1cc50 | |||
f9a46ce1e7 | |||
b6ba7f97fd | |||
7a47905f11 | |||
683f4c35d9 | |||
dfa5173cf4 | |||
04b214bef6 | |||
37cb7fec77 | |||
8833969e4a | |||
bda238267c | |||
d07dc57537 | |||
d0a2888e88 | |||
cec2eff933 | |||
38b7a3e32b | |||
9dfb6c023f | |||
cde92a9fb9 | |||
5622bbdd48 | |||
3d79a9c37a | |||
a2a5b30568 | |||
768adb84a4 | |||
26b0250e22 | |||
6893850fce | |||
8834e6905e | |||
1d5f13ddca | |||
d12c16a331 | |||
ecf47bb3ab | |||
a4bb5d4ff5 | |||
e9ee7bda46 | |||
1d196394f6 | |||
cfda67ff82 | |||
59510a85d1 | |||
35edf22ac3 | |||
871fc72892 | |||
1fcf671ca4 | |||
ecebe1314a | |||
bda5db59c8 | |||
4526d757b6 | |||
e5405d7f5c | |||
201506a5ad | |||
49f9253ca2 | |||
efc879b955 | |||
3fa03eb7a4 | |||
24bad78607 | |||
8de4c9dbb7 | |||
f858e854bf | |||
87dbd3d5ac | |||
fe66b4c8ea | |||
8390cc97e1 | |||
c0a7d4e2a7 | |||
ce23a672d9 | |||
9851317aeb | |||
3fb4a5d6e6 | |||
340e701124 | |||
36938a4407 | |||
6a6589a357 | |||
b94a32e523 | |||
7db3c69984 | |||
5406450c42 | |||
d6a6e16d21 | |||
ea1b65916d | |||
cd9d9ad50b | |||
552272b37e | |||
388ce738e3 | |||
ef7fbcbe9f | |||
80941ace37 | |||
f317500873 | |||
911414a190 | |||
cca6360bcc | |||
f68503fa21 | |||
911b69dff0 | |||
4115634bfc | |||
8a0bdde17a | |||
a1e21828d6 | |||
0f193c2337 | |||
526d94d862 | |||
2fdafa52b1 | |||
f52c0655c7 | |||
97331c7b25 | |||
1fb5a419a7 | |||
4e9afd6698 | |||
8f9dd6516e | |||
e4226def16 | |||
c199a84dbb | |||
5a4ca11362 | |||
f2968c8385 | |||
8d01b019f4 | |||
bf87330d6e | |||
2bb85bdbd4 | |||
8f34c6eeda | |||
ac5543bad9 | |||
e4c56a25c6 | |||
11ff8190b1 | |||
9bd25d7427 | |||
6bfb4207c4 | |||
c63ad610f5 | |||
e38a4323b4 | |||
d40aea5d0a | |||
fbb65cde44 |
@ -1,53 +0,0 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux-stable:
|
||||
image: ubuntu-16.04
|
||||
style: 'unflagged'
|
||||
macos-stable:
|
||||
image: macos-10.14
|
||||
style: 'unflagged'
|
||||
windows-stable:
|
||||
image: vs2017-win2016
|
||||
style: 'unflagged'
|
||||
linux-nightly-canary:
|
||||
image: ubuntu-16.04
|
||||
style: 'canary'
|
||||
macos-nightly-canary:
|
||||
image: macos-10.14
|
||||
style: 'canary'
|
||||
windows-nightly-canary:
|
||||
image: vs2017-win2016
|
||||
style: 'canary'
|
||||
fmt:
|
||||
image: ubuntu-16.04
|
||||
style: 'fmt'
|
||||
|
||||
pool:
|
||||
vmImage: $(image)
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
set -e
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
fi
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
rustup update
|
||||
rustc -Vv
|
||||
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
rustup component add rustfmt --toolchain "stable"
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all-features
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all-features
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
@ -1,3 +0,0 @@
|
||||
[build]
|
||||
|
||||
#rustflags = ["--cfg", "coloring_in_tokens"]
|
14
.cargo/config.toml
Normal file
14
.cargo/config.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
# 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
|
||||
|
||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||
# [target.x86_64-unknown-linux-gnu]
|
||||
# rustflags = ["-C", "link-args=-Wl,-z stack-size=0x80000000"]
|
||||
|
||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||
# [target.x86_64-apple-darwin]
|
||||
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
@ -1,165 +0,0 @@
|
||||
# CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/configuration-reference/ for more details
|
||||
# See https://circleci.com/docs/2.0/config-intro/#section=configuration for spec
|
||||
#
|
||||
version: 2.1
|
||||
|
||||
# Commands
|
||||
|
||||
commands:
|
||||
|
||||
pull_cache:
|
||||
description: Pulls Quay.io docker images (latest) for our cache
|
||||
parameters:
|
||||
tag:
|
||||
type: string
|
||||
default: "devel"
|
||||
steps:
|
||||
- run: echo "Tag is << parameters.tag >>"
|
||||
- run: docker pull quay.io/nushell/nu:<< parameters.tag >>
|
||||
- run: docker pull quay.io/nushell/nu-base:<< parameters.tag >>
|
||||
|
||||
orbs:
|
||||
# https://circleci.com/orbs/registry/orb/circleci/docker
|
||||
docker: circleci/docker@0.5.13
|
||||
|
||||
workflows:
|
||||
version: 2.0
|
||||
|
||||
# This builds on all pull requests to test, and ignores master
|
||||
build_without_deploy:
|
||||
jobs:
|
||||
- docker/publish:
|
||||
deploy: false
|
||||
image: nushell/nu-base
|
||||
tag: latest
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
before_build:
|
||||
- pull_cache
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu .
|
||||
- run:
|
||||
name: Preview Docker Tag for Nushell Build
|
||||
command: |
|
||||
DOCKER_TAG=$(docker run quay.io/nushell/nu --version | cut -d' ' -f2)
|
||||
echo "Version that would be used for Docker tag is v${DOCKER_TAG}"
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu-base --help
|
||||
docker run --rm quay.io/nushell/nu --help
|
||||
|
||||
# workflow publishes to Docker Hub, with each job having different triggers
|
||||
build_with_deploy:
|
||||
jobs:
|
||||
|
||||
# Deploy versioned and latest images on tags (releases) only - builds --release.
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: latest
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest --build-arg RELEASE=true
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v.*/
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:latest
|
||||
- run: docker pull quay.io/nushell/nu-base:latest
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu --help
|
||||
docker run --rm quay.io/nushell/nu-base --help
|
||||
- run:
|
||||
name: Publish Docker Tag with Nushell Version
|
||||
command: |
|
||||
DOCKER_TAG=$(docker run quay.io/nushell/nu --version | cut -d' ' -f2)
|
||||
echo "Version for Docker tag is ${DOCKER_TAG}"
|
||||
docker tag quay.io/nushell/nu-base:latest quay.io/nushell/nu-base:${DOCKER_TAG}
|
||||
docker tag quay.io/nushell/nu:latest quay.io/nushell/nu:${DOCKER_TAG}
|
||||
docker push quay.io/nushell/nu-base
|
||||
docker push quay.io/nushell/nu
|
||||
|
||||
|
||||
# publish devel to Docker Hub on merge to master (doesn't build --release)
|
||||
build_with_deploy_devel:
|
||||
jobs:
|
||||
|
||||
# Deploy devel tag on merge to master
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: devel
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
|
||||
before_build:
|
||||
- pull_cache
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build --build-arg FROMTAG=devel -f docker/Dockerfile -t quay.io/nushell/nu:devel .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu:devel --help
|
||||
docker run --rm quay.io/nushell/nu-base:devel --help
|
||||
- run:
|
||||
name: Publish Development Docker Tags
|
||||
command: |
|
||||
docker push quay.io/nushell/nu-base:devel
|
||||
docker push quay.io/nushell/nu:devel
|
||||
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
jobs:
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: nightly
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:nightly --build-arg RELEASE=true
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:nightly
|
||||
- run: docker pull quay.io/nushell/nu-base:nightly
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu:nightly .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu:nightly --help
|
||||
docker run --rm quay.io/nushell/nu-base:nightly --help
|
||||
- run:
|
||||
name: Publish Nightly Nushell Containers
|
||||
command: |
|
||||
docker push quay.io/nushell/nu-base:nightly
|
||||
docker push quay.io/nushell/nu:nightly
|
@ -1,14 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
end_of_line = lf
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Configuration (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Version [e.g. 0.4.0]
|
||||
- Optional features (if any)
|
||||
|
||||
Add any other context about the problem here.
|
63
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Thank you for your bug report.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: Steps to reproduce the behavior
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: I expected nu to...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Please add any relevant screenshots here, if any
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: "Please run `version | transpose key value | to md --pretty` and paste the output to show OS, features, etc."
|
||||
placeholder: |
|
||||
> version | transpose key value | to md --pretty
|
||||
| key | value |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| version | 0.40.0 |
|
||||
| build_os | linux-x86_64 |
|
||||
| rust_version | rustc 1.56.1 |
|
||||
| cargo_version | cargo 1.56.0 |
|
||||
| pkg_version | 0.40.0 |
|
||||
| build_time | 1980-01-01 00:00:00 +00:00 |
|
||||
| build_rust_channel | release |
|
||||
| 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 |
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
34
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Feature Request
|
||||
description: "When you want a new feature for something that doesn't already exist"
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Related problem
|
||||
description: Thank you for your feature request.
|
||||
placeholder: |
|
||||
A clear and concise description of what the problem is.
|
||||
Example: I am trying to do [...] but [...]
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: desired
|
||||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context and details
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
17
.github/pull_request_template.md
vendored
Normal file
17
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Description
|
||||
|
||||
(description of your pull request here)
|
||||
|
||||
# Tests
|
||||
|
||||
Make sure you've done the following:
|
||||
|
||||
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
|
||||
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
|
||||
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
|
||||
|
||||
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 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 test --workspace --features=extra` to check that all the tests pass
|
180
.github/workflows/ci.yml
vendored
Normal file
180
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
on:
|
||||
pull_request:
|
||||
push: # Run CI on the main branch after every merge. This is important to fill the GitHub Actions cache in a way that pull requests can see it
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: continuous-integration
|
||||
|
||||
jobs:
|
||||
nu-fmt-clippy:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
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
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
nu-tests:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
style: [extra, default]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: extra
|
||||
flags: "--features=extra"
|
||||
- style: default
|
||||
flags: ""
|
||||
exclude:
|
||||
- platform: windows-latest
|
||||
style: default
|
||||
- platform: macos-latest
|
||||
style: default
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
|
||||
# TODO: investigate why the cache was so big
|
||||
# - uses: Swatinem/rust-cache@v1
|
||||
# with:
|
||||
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||
|
||||
python-virtualenv:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
rust:
|
||||
- stable
|
||||
py:
|
||||
- py
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
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
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --path=. --profile ci --no-default-features
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- run: python -m pip install tox
|
||||
|
||||
- name: Install virtualenv
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
|
||||
shell: bash
|
||||
|
||||
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
|
||||
# also helps test that the plugins build without any feature unification shenanigans
|
||||
plugins:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --profile ci --package nu_plugin_*
|
118
.github/workflows/docker-publish.yml
vendored
118
.github/workflows/docker-publish.yml
vendored
@ -1,118 +0,0 @@
|
||||
name: Publish consumable Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v?[0-9]+.[0-9]+.[0-9]+*']
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install rust-embedded/cross
|
||||
env: { VERSION: v0.1.16 }
|
||||
run: >-
|
||||
wget -nv https://github.com/rust-embedded/cross/releases/download/${VERSION}/cross-${VERSION}-x86_64-unknown-linux-gnu.tar.gz
|
||||
-O- | sudo tar xz -C /usr/local/bin/
|
||||
- name: compile for specific target
|
||||
env: { arch: '${{ matrix.arch }}' }
|
||||
run: |
|
||||
cross build --target ${{ matrix.arch }} --release
|
||||
# leave only the executable file
|
||||
rm -rd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
|
||||
find . -empty -delete
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: ${{ matrix.arch }}
|
||||
path: target/${{ matrix.arch }}/release
|
||||
|
||||
docker:
|
||||
name: Build and publish docker images
|
||||
needs: compile
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_REGISTRY: quay.io/nushell
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_REGISTRY }}
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
strategy:
|
||||
matrix:
|
||||
tag:
|
||||
- alpine
|
||||
- slim
|
||||
- debian
|
||||
- glibc-busybox
|
||||
- musl-busybox
|
||||
- musl-distroless
|
||||
- glibc-distroless
|
||||
- glibc
|
||||
- musl
|
||||
include:
|
||||
- { tag: alpine, base-image: alpine, arch: x86_64-unknown-linux-musl, plugin: true }
|
||||
- { tag: slim, base-image: 'debian:stable-slim', arch: x86_64-unknown-linux-gnu, plugin: true }
|
||||
- { tag: debian, base-image: debian, arch: x86_64-unknown-linux-gnu, plugin: true }
|
||||
- { tag: glibc-busybox, base-image: 'busybox:glibc', arch: x86_64-unknown-linux-gnu, use-patch: true }
|
||||
- { tag: musl-busybox, base-image: 'busybox:musl', arch: x86_64-unknown-linux-musl, }
|
||||
- { tag: musl-distroless, base-image: 'gcr.io/distroless/static', arch: x86_64-unknown-linux-musl, }
|
||||
- { tag: glibc-distroless, base-image: 'gcr.io/distroless/cc', arch: x86_64-unknown-linux-gnu, use-patch: true }
|
||||
- { tag: glibc, base-image: scratch, arch: x86_64-unknown-linux-gnu, }
|
||||
- { tag: musl, base-image: scratch, arch: x86_64-unknown-linux-musl, }
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/download-artifact@master
|
||||
with: { name: '${{ matrix.arch }}', path: target/release }
|
||||
- name: Build and publish exact version
|
||||
run: |-
|
||||
export DOCKER_TAG=${GITHUB_REF##*/}-${{ matrix.tag }}
|
||||
export NU_BINS=target/release/$( [ ${{ matrix.plugin }} = true ] && echo nu* || echo nu )
|
||||
export PATCH=$([ ${{ matrix.use-patch }} = true ] && echo .${{ matrix.tag }} || echo '')
|
||||
chmod +x $NU_BINS
|
||||
|
||||
echo ${DOCKER_PASSWORD} | docker login ${DOCKER_REGISTRY} -u ${DOCKER_USER} --password-stdin
|
||||
docker-compose --file docker/docker-compose.package.yml build
|
||||
docker-compose --file docker/docker-compose.package.yml push # exact version
|
||||
env:
|
||||
BASE_IMAGE: ${{ matrix.base-image }}
|
||||
|
||||
#region semantics tagging
|
||||
- name: Retag and push with suffixed version
|
||||
run: |-
|
||||
VERSION=${GITHUB_REF##*/}
|
||||
|
||||
latest_version=${VERSION%%%.*}-${{ matrix.tag }}
|
||||
latest_feature=${VERSION%%.*}-${{ matrix.tag }}
|
||||
latest_patch=${VERSION%.*}-${{ matrix.tag }}
|
||||
exact_version=${VERSION}-${{ matrix.tag }}
|
||||
|
||||
tags=( ${latest_version} ${latest_feature} ${latest_patch} ${exact_version} )
|
||||
|
||||
for tag in ${tags[@]}; do
|
||||
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${tag}
|
||||
docker push ${DOCKER_REGISTRY}/nu:${tag}
|
||||
done
|
||||
|
||||
# latest version
|
||||
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${{ matrix.tag }}
|
||||
docker push ${DOCKER_REGISTRY}/nu:${{ matrix.tag }}
|
||||
|
||||
- name: Retag and push debian as latest
|
||||
if: matrix.tag == 'debian'
|
||||
run: |-
|
||||
VERSION=${GITHUB_REF##*/}
|
||||
|
||||
# ${latest features} ${latest patch} ${exact version}
|
||||
tags=( ${VERSION%%.*} ${VERSION%.*} ${VERSION} )
|
||||
|
||||
for tag in ${tags[@]}; do
|
||||
docker tag ${DOCKER_REGISTRY}/nu:${VERSION}-${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:${tag}
|
||||
docker push ${DOCKER_REGISTRY}/nu:${tag}
|
||||
done
|
||||
|
||||
# latest version
|
||||
docker tag ${DOCKER_REGISTRY}/nu:${{ matrix.tag }} ${DOCKER_REGISTRY}/nu:latest
|
||||
docker push ${DOCKER_REGISTRY}/nu:latest
|
||||
#endregion semantics tagging
|
155
.github/workflows/release-pkg.nu
vendored
Executable file
155
.github/workflows/release-pkg.nu
vendored
Executable file
@ -0,0 +1,155 @@
|
||||
#!/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
|
||||
|
||||
# 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)
|
||||
|
||||
# $env
|
||||
|
||||
$'(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 ['ubuntu-latest', 'macos-latest'] {
|
||||
if $os == 'ubuntu-latest' {
|
||||
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 {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
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 | empty?) {
|
||||
cargo build --release --all --target $target --features=extra
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra $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 collect
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
}
|
||||
if ($ver | str trim | 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 ['ubuntu-latest', 'macos-latest'] {
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
|
||||
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
|
||||
tar czf $archive *
|
||||
print $'archive: ---> ($archive)'; ls $archive
|
||||
echo $'::set-output name=archive::($archive)'
|
||||
|
||||
} 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-v590/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.2
|
||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||
echo $'::set-output name=archive::($wixRelease)'
|
||||
|
||||
} 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 | empty?) {
|
||||
echo $'::set-output name=archive::($pkg | get 0)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def 'cargo-build-nu' [ options: string ] {
|
||||
if ($options | str trim | empty?) {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra,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
|
||||
}
|
97
.github/workflows/release.yml
vendored
Normal file
97
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
#
|
||||
# REF:
|
||||
# 1. https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
|
||||
#
|
||||
name: Create Release Draft
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
all:
|
||||
name: All
|
||||
|
||||
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
|
||||
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-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install Rust Toolchain Components
|
||||
uses: actions-rs/toolchain@v1.0.6
|
||||
with:
|
||||
override: true
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v1
|
||||
with:
|
||||
version: 0.63.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
_EXTRA_: ${{ matrix.extra }}
|
||||
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
|
||||
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ steps.nu.outputs.archive }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
28
.github/workflows/stale.yml
vendored
Normal file
28
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
#on: [workflow_dispatch]
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
#debug-only: true
|
||||
ascending: true
|
||||
operations-per-run: 520
|
||||
enable-statistics: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
|
||||
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
|
||||
days-before-issue-stale: 90
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 100000
|
||||
days-before-pr-close: 100
|
||||
exempt-issue-labels: 'exempt,keep'
|
19
.github/workflows/winget-submission.yml
vendored
Normal file
19
.github/workflows/winget-submission.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
|
||||
winget:
|
||||
name: Publish winget package
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$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 }}
|
19
.gitignore
vendored
19
.gitignore
vendored
@ -3,6 +3,8 @@
|
||||
**/*.rs.bk
|
||||
history.txt
|
||||
tests/fixtures/nuplayground
|
||||
crates/*/target
|
||||
.mailmap
|
||||
|
||||
# Debian/Ubuntu
|
||||
debian/.debhelper/
|
||||
@ -10,3 +12,20 @@ debian/debhelper-build-stamp
|
||||
debian/files
|
||||
debian/nu.substvars
|
||||
debian/nu/
|
||||
|
||||
# macOS junk
|
||||
.DS_Store
|
||||
|
||||
# JetBrains' IDE items
|
||||
.idea/*
|
||||
|
||||
# VSCode's IDE items
|
||||
.vscode/*
|
||||
|
||||
# Helix configuration folder
|
||||
.helix/*
|
||||
.helix
|
||||
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
7
.gitpod.Dockerfile
vendored
7
.gitpod.Dockerfile
vendored
@ -1,7 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
USER root
|
||||
RUN apt-get update && apt-get install -y libssl-dev \
|
||||
libxcb-composite0-dev \
|
||||
pkg-config \
|
||||
curl \
|
||||
rustc
|
21
.gitpod.yml
21
.gitpod.yml
@ -1,21 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
tasks:
|
||||
- init: cargo install nu
|
||||
command: nu
|
||||
github:
|
||||
prebuilds:
|
||||
# enable for the master/default branch (defaults to true)
|
||||
master: true
|
||||
# enable for all branches in this repo (defaults to false)
|
||||
branches: true
|
||||
# enable for pull requests coming from this repo (defaults to true)
|
||||
pullRequests: true
|
||||
# enable for pull requests coming from forks (defaults to false)
|
||||
pullRequestsFromForks: true
|
||||
# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)
|
||||
addComment: true
|
||||
# add a "Review in Gitpod" button to pull requests (defaults to false)
|
||||
addBadge: false
|
||||
# add a label once the prebuild is ready to pull requests (defaults to false)
|
||||
addLabel: prebuilt-in-gitpod
|
@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at wycats@gmail.com. All
|
||||
reported by contacting the project team at wycats@gmail.com via email or by reaching out to @jturner, @gedge, or @andras_io on discord. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
@ -68,9 +68,9 @@ members of the project's leadership.
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
<https://www.contributor-covenant.org/faq>
|
||||
|
68
CONTRIBUTING.md
Normal file
68
CONTRIBUTING.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to Nushell!
|
||||
|
||||
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)!
|
||||
|
||||
## Developing
|
||||
|
||||
### Setup
|
||||
|
||||
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
|
||||
git clone https://github.com/nushell/nushell
|
||||
cd nushell
|
||||
cargo build
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo run
|
||||
```
|
||||
|
||||
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
|
||||
```shell
|
||||
cargo run --features=extra
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --workspace --features=extra
|
||||
```
|
||||
|
||||
- Run all tests for a specific command
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
- Check to see if there are code formatting issues
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
- Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo run --release --features=extra -- --log-level trace
|
||||
```
|
5907
Cargo.lock
generated
5907
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
282
Cargo.toml
282
Cargo.toml
@ -1,206 +1,122 @@
|
||||
[package]
|
||||
name = "nu"
|
||||
version = "0.6.1"
|
||||
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
|
||||
description = "A shell for the GitHub era"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
edition = "2018"
|
||||
exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
documentation = "https://book.nushell.sh"
|
||||
|
||||
[workspace]
|
||||
|
||||
members = ["crates/nu-source"]
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.66.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/nu-cli",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-command",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu_plugin_inc",
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
nu-source = { version = "0.1.0", path = "./crates/nu-source" }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossterm = "0.23.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.66.2" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.66.2" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.66.3" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.66.2" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.66.2" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.66.2" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.66.2" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.66.2" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.66.2" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.66.2" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.66.2" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.66.2" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.2" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.66.2" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
pretty_env_logger = "0.4.0"
|
||||
rayon = "1.5.1"
|
||||
is_executable = "1.0.1"
|
||||
|
||||
rustyline = "5.0.4"
|
||||
chrono = { version = "0.4.9", features = ["serde"] }
|
||||
derive-new = "0.5.8"
|
||||
prettytable-rs = "0.8.0"
|
||||
itertools = "0.8.1"
|
||||
ansi_term = "0.12.1"
|
||||
nom = "5.0.1"
|
||||
dunce = "1.0.0"
|
||||
indexmap = { version = "1.3.0", features = ["serde-1"] }
|
||||
chrono-humanize = "0.0.11"
|
||||
byte-unit = "3.0.3"
|
||||
base64 = "0.11"
|
||||
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
|
||||
async-stream = "0.1.2"
|
||||
futures_codec = "0.2.5"
|
||||
num-traits = "0.2.8"
|
||||
term = "0.5.2"
|
||||
bytes = "0.4.12"
|
||||
log = "0.4.8"
|
||||
pretty_env_logger = "0.3.1"
|
||||
serde = { version = "1.0.102", features = ["derive"] }
|
||||
bson = { version = "0.14.0", features = ["decimal128"] }
|
||||
serde_json = "1.0.41"
|
||||
serde-hjson = "0.9.1"
|
||||
serde_yaml = "0.8"
|
||||
serde_bytes = "0.11.2"
|
||||
getset = "0.0.9"
|
||||
language-reporting = "0.4.0"
|
||||
app_dirs = "1.2.1"
|
||||
csv = "1.1"
|
||||
toml = "0.5.5"
|
||||
clap = "2.33.0"
|
||||
git2 = { version = "0.10.1", default_features = false }
|
||||
dirs = "2.0.2"
|
||||
glob = "0.3.0"
|
||||
ctrlc = "3.1.3"
|
||||
surf = "1.0.3"
|
||||
url = "2.1.0"
|
||||
roxmltree = "0.7.2"
|
||||
nom_locate = "1.0.0"
|
||||
nom-tracable = "0.4.1"
|
||||
unicode-xid = "0.2.0"
|
||||
serde_ini = "0.2.0"
|
||||
subprocess = "0.1.18"
|
||||
mime = "0.3.14"
|
||||
pretty-hex = "0.1.1"
|
||||
hex = "0.4"
|
||||
tempfile = "3.1.0"
|
||||
semver = "0.9.0"
|
||||
which = "3.1"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
shellexpand = "1.0.0"
|
||||
futures-timer = "2.0.0"
|
||||
pin-utils = "0.1.0-alpha.4"
|
||||
num-bigint = { version = "0.2.3", features = ["serde"] }
|
||||
bigdecimal = { version = "0.1.0", features = ["serde"] }
|
||||
natural = "0.3.0"
|
||||
serde_urlencoded = "0.6.1"
|
||||
sublime_fuzzy = "0.6"
|
||||
trash = "1.0.0"
|
||||
regex = "1"
|
||||
cfg-if = "0.1"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
calamine = "0.16"
|
||||
umask = "0.1"
|
||||
futures-util = "0.3.0"
|
||||
pretty = "0.5.2"
|
||||
termcolor = "1.0.5"
|
||||
console = "0.9.1"
|
||||
|
||||
neso = { version = "0.5.0", optional = true }
|
||||
crossterm = { version = "0.10.2", optional = true }
|
||||
syntect = {version = "3.2.0", optional = true }
|
||||
onig_sys = {version = "=69.1.0", optional = true }
|
||||
heim = {version = "0.0.8", optional = true }
|
||||
battery = {version = "0.7.4", optional = true }
|
||||
rawkey = {version = "0.1.2", optional = true }
|
||||
clipboard = {version = "0.5", optional = true }
|
||||
ptree = {version = "0.2" }
|
||||
image = { version = "0.22.2", default_features = false, features = ["png_codec", "jpeg"], optional = true }
|
||||
starship = { version = "0.26.4", optional = true}
|
||||
|
||||
[features]
|
||||
default = ["textview", "sys", "ps"]
|
||||
raw-key = ["rawkey", "neso"]
|
||||
textview = ["syntect", "onig_sys", "crossterm"]
|
||||
binaryview = ["image", "crossterm"]
|
||||
sys = ["heim", "battery"]
|
||||
ps = ["heim"]
|
||||
starship-prompt = ["starship"]
|
||||
# trace = ["nom-tracable/trace"]
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.20.0"
|
||||
features = ["bundled", "blob"]
|
||||
[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]
|
||||
pretty_assertions = "0.6.1"
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.66.2" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.8.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.15.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[build-dependencies]
|
||||
toml = "0.5.5"
|
||||
serde = { version = "1.0.102", features = ["derive"] }
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[lib]
|
||||
name = "nu"
|
||||
path = "src/lib.rs"
|
||||
[features]
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
default = ["plugin", "which-support", "trash-support"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
wasi = []
|
||||
# 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"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_inc"
|
||||
path = "src/plugins/inc.rs"
|
||||
# Stable (Default)
|
||||
which-support = ["nu-command/which-support"]
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_sum"
|
||||
path = "src/plugins/sum.rs"
|
||||
# Extra
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_average"
|
||||
path = "src/plugins/average.rs"
|
||||
# Dataframe feature for nushell
|
||||
dataframe = ["nu-command/dataframe"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_embed"
|
||||
path = "src/plugins/embed.rs"
|
||||
# Database commands for nushell
|
||||
database = ["nu-command/database"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_insert"
|
||||
path = "src/plugins/insert.rs"
|
||||
[profile.release]
|
||||
opt-level = "s" # Optimize for size
|
||||
strip = "debuginfo"
|
||||
lto = "thin"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_edit"
|
||||
path = "src/plugins/edit.rs"
|
||||
# build with `cargo build --profile profiling`
|
||||
# to analyze performance with tooling like linux perf
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_format"
|
||||
path = "src/plugins/format.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_parse"
|
||||
path = "src/plugins/parse.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_str"
|
||||
path = "src/plugins/str.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_skip"
|
||||
path = "src/plugins/skip.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_match"
|
||||
path = "src/plugins/match.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_sys"
|
||||
path = "src/plugins/sys.rs"
|
||||
required-features = ["sys"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_ps"
|
||||
path = "src/plugins/ps.rs"
|
||||
required-features = ["ps"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_tree"
|
||||
path = "src/plugins/tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_binaryview"
|
||||
path = "src/plugins/binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_textview"
|
||||
path = "src/plugins/textview.rs"
|
||||
required-features = ["textview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_docker"
|
||||
path = "src/plugins/docker.rs"
|
||||
required-features = ["docker"]
|
||||
# build with `cargo build --profile ci`
|
||||
# to analyze performance with tooling like linux perf
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
strip = false
|
||||
debug = false
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Yehuda Katz, Jonathan Turner
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,24 +0,0 @@
|
||||
[tasks.lalrpop]
|
||||
install_crate = { crate_name = "lalrpop", binary = "lalrpop", test_arg = "--help" }
|
||||
command = "lalrpop"
|
||||
args = ["src/parser/parser.lalrpop"]
|
||||
|
||||
[tasks.baseline]
|
||||
command = "cargo"
|
||||
args = ["build", "--bins"]
|
||||
|
||||
[tasks.run]
|
||||
command = "cargo"
|
||||
args = ["run"]
|
||||
dependencies = ["baseline"]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test"]
|
||||
dependencies = ["baseline"]
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
args = ["check"]
|
||||
dependencies = ["baseline"]
|
||||
|
440
README.md
440
README.md
@ -1,343 +1,231 @@
|
||||
# Nushell <!-- omit in toc -->
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
||||

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

|
||||

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

|
||||
|
||||
A modern shell for the GitHub era.
|
||||
## 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. 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.
|
||||
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.
|
||||
|
||||
Nu comes with a set of built-in commands (listed below). If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
||||
## Learning About Nu
|
||||
|
||||
# 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. There is a [book](https://book.nushell.sh) about Nu that is currently in progress. The book focuses on using Nu and its core concepts.
|
||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||
|
||||
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://github.com/nushell/contributor-book/tree/master/en) 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.
|
||||
## Installation
|
||||
|
||||
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.
|
||||
|
||||
Try it in Gitpod.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
|
||||
# Installation
|
||||
|
||||
## Local
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://book.nushell.sh/en/installation). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.39 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
* pkg-config and libssl (only needed on Linux)
|
||||
* on Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
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`):
|
||||
|
||||
```
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
You can also install Nu with all the bells and whistles (be sure to have installed the [dependencies](https://book.nushell.sh/en/installation#dependencies) for your platform):
|
||||
|
||||
```
|
||||
cargo install nu --all-features
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
||||
on Quay.io. Pulling a container would come down to:
|
||||
To quickly install Nu:
|
||||
|
||||
```bash
|
||||
$ docker pull quay.io/nushell/nu
|
||||
$ docker pull quay.io/nushell/nu-base
|
||||
# Linux and macOS
|
||||
brew install nushell
|
||||
# Windows
|
||||
winget install nushell
|
||||
```
|
||||
|
||||
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
||||
in the container and all dependencies.
|
||||
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
|
||||
Optionally, you can also build the containers locally using the [dockerfiles provided](docker):
|
||||
To build the base image:
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
```
|
||||
|
||||
And then to build the smaller container (using a Multistage build):
|
||||
|
||||
```bash
|
||||
$ docker build -f docker/Dockerfile -t nushell/nu .
|
||||
```
|
||||
|
||||
Either way, you can run either container as follows:
|
||||
|
||||
```bash
|
||||
$ docker run -it nushell/nu-base
|
||||
$ docker run -it nushell/nu
|
||||
/> exit
|
||||
```
|
||||
|
||||
The second container is a bit smaller if the size is important to you.
|
||||
|
||||
## Packaging status
|
||||
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:
|
||||
|
||||
[](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. Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure. For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory. These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||
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.
|
||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
|
||||
## Pipelines
|
||||
### Pipelines
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps. Nu takes this a step further and builds heavily on the idea of _pipelines_. Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin. Additionally, commands can output structured data (you can think of this as a third kind of stream). Commands that work in the pipeline fit into one of three categories:
|
||||
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_.
|
||||
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).
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
* Commands that produce a stream (eg, `ls`)
|
||||
* Commands that filter a stream (eg, `where type == "Directory"`)
|
||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
||||
- 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.
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> ls | where type == "Directory" | autoview
|
||||
━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━
|
||||
# │ name │ type │ readonly │ size │ accessed │ modified
|
||||
────┼───────────┼───────────┼──────────┼────────┼──────────────┼────────────────
|
||||
0 │ .azure │ Directory │ │ 4.1 KB │ 2 months ago │ a day ago
|
||||
1 │ target │ Directory │ │ 4.1 KB │ 3 days ago │ 3 days ago
|
||||
2 │ images │ Directory │ │ 4.1 KB │ 2 months ago │ 2 weeks ago
|
||||
3 │ tests │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago
|
||||
4 │ tmp │ Directory │ │ 4.1 KB │ 2 weeks ago │ 2 weeks ago
|
||||
5 │ src │ Directory │ │ 4.1 KB │ 2 months ago │ 37 minutes ago
|
||||
6 │ assets │ Directory │ │ 4.1 KB │ a month ago │ a month ago
|
||||
7 │ docs │ Directory │ │ 4.1 KB │ 2 months ago │ 2 months ago
|
||||
━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━
|
||||
```shell
|
||||
> ls | where type == "dir" | table
|
||||
╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||
│ # │ name │ type │ size │ modified │
|
||||
├────┼──────────┼──────┼─────────┼───────────────┤
|
||||
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks 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. We could have also written the above:
|
||||
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:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> ls | where type == Directory
|
||||
```shell
|
||||
> ls | where type == "dir"
|
||||
```
|
||||
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu. For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
||||
|
||||
```text
|
||||
/home/jonathan/Source/nushell(master)> ps | where cpu > 0
|
||||
━━━┯━━━━━━━┯━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━━
|
||||
# │ pid │ name │ status │ cpu
|
||||
───┼───────┼─────────────────┼──────────┼──────────
|
||||
0 │ 992 │ chrome │ Sleeping │ 6.988768
|
||||
1 │ 4240 │ chrome │ Sleeping │ 5.645982
|
||||
2 │ 13973 │ qemu-system-x86 │ Sleeping │ 4.996551
|
||||
3 │ 15746 │ nu │ Sleeping │ 84.59905
|
||||
━━━┷━━━━━━━┷━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━━
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
||||
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||
│ # │ pid │ name │ cpu │ mem │ virtual │
|
||||
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||
```
|
||||
|
||||
## Opening files
|
||||
### Opening files
|
||||
|
||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format). For example, you can load a .toml file as structured data and explore it:
|
||||
Nu can load file and URL contents as raw text or structured data (if it recognizes the format).
|
||||
For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml
|
||||
━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━
|
||||
bin │ dependencies │ dev-dependencies
|
||||
──────────────────┼────────────────┼──────────────────
|
||||
[table: 12 rows] │ [table: 1 row] │ [table: 1 row]
|
||||
━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━
|
||||
```shell
|
||||
> open Cargo.toml
|
||||
╭──────────────────┬────────────────────╮
|
||||
│ bin │ [table 1 row] │
|
||||
│ dependencies │ {record 24 fields} │
|
||||
│ dev-dependencies │ {record 8 fields} │
|
||||
│ features │ {record 10 fields} │
|
||||
│ package │ {record 13 fields} │
|
||||
│ profile │ {record 3 fields} │
|
||||
│ target │ {record 2 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:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package
|
||||
━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━┯━━━━━━┯━━━━━━━━━
|
||||
authors │ description │ edition │ license │ name │ version
|
||||
─────────────────┼────────────────────────────┼─────────┼─────────┼──────┼─────────
|
||||
[table: 3 rows] │ A shell for the GitHub era │ 2018 │ MIT │ nu │ 0.6.1
|
||||
━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━┷━━━━━━┷━━━━━━━━━
|
||||
```shell
|
||||
> open Cargo.toml | get package
|
||||
╭───────────────┬────────────────────────────────────╮
|
||||
│ authors │ [list 1 item] │
|
||||
│ default-run │ nu │
|
||||
│ description │ A new type of shell │
|
||||
│ documentation │ https://www.nushell.sh/book/ │
|
||||
│ edition │ 2018 │
|
||||
│ exclude │ [list 1 item] │
|
||||
│ homepage │ https://www.nushell.sh │
|
||||
│ license │ MIT │
|
||||
│ name │ nu │
|
||||
│ readme │ README.md │
|
||||
│ repository │ https://github.com/nushell/nushell │
|
||||
│ rust-version │ 1.60 │
|
||||
│ version │ 0.63.1 │
|
||||
╰───────────────┴────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
And if needed we can drill down further:
|
||||
|
||||
```
|
||||
/home/jonathan/Source/nushell(master)> open Cargo.toml | get package.version | echo $it
|
||||
0.6.1
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.63.1
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
### Plugins
|
||||
|
||||
## Configuration
|
||||
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.
|
||||
|
||||
Nu has early support for configuring the shell. It currently supports the following settings:
|
||||
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.
|
||||
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.
|
||||
|
||||
| Variable | Type | Description |
|
||||
| ------------- | ------------- | ----- |
|
||||
| path | table of strings | PATH to use to find binaries |
|
||||
| env | row | the environment variables to pass to external commands |
|
||||
| ctrlc_exit | boolean | whether or not to exit Nu after multiple ctrl-c presses |
|
||||
| table_mode | "light" or other | enable lightweight or normal tables |
|
||||
| edit_mode | "vi" or "emacs" | changes line editing to "vi" or "emacs" mode |
|
||||
|
||||
To set one of these variables, you can use `config --set`. For example:
|
||||
|
||||
```
|
||||
> config --set [edit_mode "vi"]
|
||||
> config --set [path $nu:path]
|
||||
```
|
||||
|
||||
## Shells
|
||||
|
||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default. Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
||||
|
||||
To do so, use the `enter` command, which will allow you 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
|
||||
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. This allows you to extend nu for your needs.
|
||||
|
||||
There are a few examples in the `plugins` directory.
|
||||
|
||||
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention. These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use. If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout. If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
# Goals
|
||||
## Goals
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
* First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should 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
|
||||
## Initial commands
|
||||
| command | description |
|
||||
| ------------- | ------------- |
|
||||
| cd path | Change to a new path |
|
||||
| cp source path | Copy files |
|
||||
| date (--utc) | Get the current datetime |
|
||||
| fetch url | Fetch contents from a url and retrieve data as a table if possible |
|
||||
| help | Display help information about commands |
|
||||
| ls (path) | View the contents of the current or given path |
|
||||
| mkdir path | Make directories, creates intermediary directories as required. |
|
||||
| mv source target | Move files or directories. |
|
||||
| open filename | Load a file into a cell, convert to table if possible (avoid by appending '--raw') |
|
||||
| post url body (--user <user>) (--password <password>) | Post content to a url and retrieve data as a table if possible |
|
||||
| ps | View current processes |
|
||||
| sys | View information about the current system |
|
||||
| which filename | Finds a program file. |
|
||||
| rm {file or directory} | Remove a file, (for removing directory append '--recursive') |
|
||||
| version | Display Nu version |
|
||||
## Progress
|
||||
|
||||
## Shell commands
|
||||
| command | description |
|
||||
| ------- | ----------- |
|
||||
| exit (--now) | Exit the current shell (or all shells) |
|
||||
| enter (path) | Create a new shell and begin at this path |
|
||||
| p | Go to previous shell |
|
||||
| n | Go to next shell |
|
||||
| shells | Display the list of current shells |
|
||||
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:
|
||||
|
||||
## Filters on tables (structured data)
|
||||
| command | description |
|
||||
| ------------- | ------------- |
|
||||
| append row-data | Append a row to the end of the table |
|
||||
| compact ...columns | Remove rows where given columns are empty |
|
||||
| count | Show the total number of rows |
|
||||
| default column row-data | Sets a default row's column if missing |
|
||||
| edit column-or-column-path value | Edit an existing column to have a new value |
|
||||
| embed column | Creates a new table of one column with the given name, and places the current table inside of it |
|
||||
| first amount | Show only the first number of rows |
|
||||
| format pattern | Format table row data as a string following the given pattern |
|
||||
| get column-or-column-path | Open column and get data from the corresponding cells |
|
||||
| group-by column | Creates a new table with the data from the table rows grouped by the column given |
|
||||
| histogram column ...column-names | Creates a new table with a histogram based on the column name passed in, optionally give the frequency column name
|
||||
| inc (column-or-column-path) | Increment a value or version. Optionally use the column of a table |
|
||||
| insert column-or-column-path value | Insert a new column to the table |
|
||||
| last amount | Show only the last number of rows |
|
||||
| nth ...row-numbers | Return only the selected rows |
|
||||
| pick ...columns | Down-select table to only these columns |
|
||||
| pivot --header-row <headers> | Pivot the tables, making columns into rows and vice versa |
|
||||
| prepend row-data | Prepend a row to the beginning of the table |
|
||||
| reject ...columns | Remove the given columns from the table |
|
||||
| reverse | Reverses the table. |
|
||||
| skip amount | Skip a number of rows |
|
||||
| skip-while condition | Skips rows while the condition matches |
|
||||
| split-by column | Creates a new table with the data from the inner tables splitted by the column given |
|
||||
| sort-by ...columns | Sort by the given columns |
|
||||
| str (column) | Apply string function. Optionally use the column of a table |
|
||||
| sum | Sum a column of values |
|
||||
| tags | Read the tags (metadata) for values |
|
||||
| to-bson | Convert table into .bson binary data |
|
||||
| to-csv | Convert table into .csv text |
|
||||
| to-json | Convert table into .json text |
|
||||
| to-sqlite | Convert table to sqlite .db binary data |
|
||||
| to-toml | Convert table into .toml text |
|
||||
| to-tsv | Convert table into .tsv text |
|
||||
| to-url | Convert table to a urlencoded string |
|
||||
| to-yaml | Convert table into .yaml text |
|
||||
| where condition | Filter table to match the condition |
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | | | X | | Aliases allow for shortening large commands, while passing flags |
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
|
||||
| File ops | | | | X | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | | | X | | Temporary environment and scoped environment variables |
|
||||
| Shells | | | | X | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | | X | | Streaming protocol is serviceable |
|
||||
| Plugins | | | X | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | | X | | Error reporting works, but could use usability polish |
|
||||
| Documentation | | | X | | | Book updated to latest release, including usage examples |
|
||||
| Paging | | | | X | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | | | X | | Functions and aliases are supported |
|
||||
| Variables | | | | X | | Nu supports variables and environment variables |
|
||||
| Completions | | | | X | | Completions for filepaths |
|
||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
||||
|
||||
## Filters on text (unstructured data)
|
||||
| command | description |
|
||||
| ------------- | ------------- |
|
||||
| from-bson | Parse binary data as .bson and create table |
|
||||
| from-csv | Parse text as .csv and create table |
|
||||
| from-ini | Parse text as .ini and create table |
|
||||
| from-json | Parse text as .json and create table |
|
||||
| from-sqlite | Parse binary data as sqlite .db and create table |
|
||||
| from-ssv --minimum-spaces <minimum number of spaces to count as a separator> | Parse text as space-separated values and create table |
|
||||
| from-toml | Parse text as .toml and create table |
|
||||
| from-tsv | Parse text as .tsv and create table |
|
||||
| from-url | Parse urlencoded string and create a table |
|
||||
| from-xml | Parse text as .xml and create a table |
|
||||
| from-yaml | Parse text as a .yaml/.yml and create a table |
|
||||
| lines | Split single string into rows, one per line |
|
||||
| parse pattern | Convert text to a table by matching the given pattern |
|
||||
| size | Gather word count statistics on the text |
|
||||
| split-column sep ...column-names | Split row contents across multiple columns via the separator, optionally give the columns names |
|
||||
| split-row sep | Split row contents over multiple rows via the separator |
|
||||
| trim | Trim leading and following whitespace from text data |
|
||||
| {external-command} $it | Run external command with given arguments, replacing $it with each row text |
|
||||
## Officially Supported By
|
||||
|
||||
## Consuming commands
|
||||
| command | description |
|
||||
| ------------- | ------------- |
|
||||
| autoview | View the contents of the pipeline as a table or list |
|
||||
| binaryview | Autoview of binary data (optional feature) |
|
||||
| clip | Copy the contents of the pipeline to the copy/paste buffer (optional feature) |
|
||||
| save filename | Save the contents of the pipeline to a file |
|
||||
| table | View the contents of the pipeline as a table |
|
||||
| textview | Autoview of text data |
|
||||
| tree | View the contents of the pipeline as a tree (optional feature) |
|
||||
Please submit an issue or PR to be added to this list.
|
||||
|
||||
# License
|
||||
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
||||
- [starship](https://github.com/starship/starship)
|
||||
- [oh-my-posh](https://ohmyposh.dev)
|
||||
- [Couchbase Shell](https://couchbase.sh)
|
||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||
|
||||
The project is made available under the MIT license. See "LICENSE" for more information.
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
The project is made available under the MIT license. See the `LICENSE` file for more information.
|
||||
|
3
README.release.txt
Normal file
3
README.release.txt
Normal file
@ -0,0 +1,3 @@
|
||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||
|
||||
> register -e json ./nu_plugin_query
|
48
TODO.md
48
TODO.md
@ -1,48 +0,0 @@
|
||||
This pattern is extremely repetitive and can be abstracted:
|
||||
|
||||
```rs
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
|
||||
let mut concat_string = String::new();
|
||||
let mut latest_tag: Option<Tag> = None;
|
||||
|
||||
for value in values {
|
||||
latest_tag = Some(value_tag.clone());
|
||||
let value_span = value.tag.span;
|
||||
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
concat_string.push_str(&s);
|
||||
concat_string.push_str("\n");
|
||||
}
|
||||
_ => yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name_span,
|
||||
"value originates from here",
|
||||
value_span,
|
||||
)),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Mandatory and Optional in parse_command
|
||||
|
||||
trace_remaining?
|
||||
|
||||
select_fields and select_fields take unnecessary Tag
|
||||
|
||||
Value#value should be Value#untagged
|
||||
|
||||
Unify dictionary building, probably around a macro
|
||||
|
||||
sys plugin in own crate
|
||||
|
||||
textview in own crate
|
BIN
assets/icons/nushell-original.png
Normal file
BIN
assets/icons/nushell-original.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/nu_logo.ico
Normal file
BIN
assets/nu_logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
Binary file not shown.
23
build-all-maclin.sh
Executable file
23
build-all-maclin.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "---------------------------------------------------------------"
|
||||
echo "Building nushell (nu) with --features=extra and all the plugins"
|
||||
echo "---------------------------------------------------------------"
|
||||
echo ""
|
||||
|
||||
NU_PLUGINS=(
|
||||
'nu_plugin_example'
|
||||
'nu_plugin_gstat'
|
||||
'nu_plugin_inc'
|
||||
'nu_plugin_query'
|
||||
)
|
||||
|
||||
echo "Building nushell"
|
||||
cargo build --features=extra
|
||||
for plugin in "${NU_PLUGINS[@]}"
|
||||
do
|
||||
echo '' && cd crates/$plugin
|
||||
echo "Building $plugin..."
|
||||
echo "-----------------------------"
|
||||
cargo build && cd ../..
|
||||
done
|
32
build-all-windows.cmd
Normal file
32
build-all-windows.cmd
Normal file
@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo Building nushell (nu.exe) with --features=extra and all the plugins
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo.
|
||||
|
||||
echo Building nushell.exe
|
||||
cargo build --features=extra
|
||||
@echo.
|
||||
|
||||
@cd crates\nu_plugin_example
|
||||
echo Building nu_plugin_example.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_gstat
|
||||
echo Building nu_plugin_gstat.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_inc
|
||||
echo Building nu_plugin_inc.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
22
build-all.nu
Normal file
22
build-all.nu
Normal file
@ -0,0 +1,22 @@
|
||||
echo '-------------------------------------------------------------------'
|
||||
echo 'Building nushell (nu) with --features=extra and all the plugins'
|
||||
echo '-------------------------------------------------------------------'
|
||||
|
||||
echo $'(char nl)Building nushell'
|
||||
echo '----------------------------'
|
||||
cargo build --features=extra
|
||||
|
||||
let plugins = [
|
||||
nu_plugin_inc,
|
||||
nu_plugin_gstat,
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
$'(char nl)Building ($plugin)'
|
||||
'----------------------------'
|
||||
cd $'crates/($plugin)'
|
||||
cargo build
|
||||
ignore
|
||||
}
|
48
build.rs
48
build.rs
@ -1,39 +1,13 @@
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Feature {
|
||||
#[allow(unused)]
|
||||
description: String,
|
||||
enabled: bool,
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
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)");
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let all_on = env::var("NUSHELL_ENABLE_ALL_FLAGS").is_ok();
|
||||
let flags: HashSet<String> = env::var("NUSHELL_ENABLE_FLAGS")
|
||||
.map(|s| s.split(",").map(|s| s.to_string()).collect())
|
||||
.unwrap_or_else(|_| HashSet::new());
|
||||
|
||||
if all_on && !flags.is_empty() {
|
||||
println!(
|
||||
"cargo:warning={}",
|
||||
"Both NUSHELL_ENABLE_ALL_FLAGS and NUSHELL_ENABLE_FLAGS were set. You don't need both."
|
||||
);
|
||||
}
|
||||
|
||||
let path = Path::new(&input).join("features.toml");
|
||||
|
||||
let toml: HashMap<String, Feature> = toml::from_str(&std::fs::read_to_string(path)?)?;
|
||||
|
||||
for (key, value) in toml.iter() {
|
||||
if value.enabled == true || all_on || flags.contains(key) {
|
||||
println!("cargo:rustc-cfg={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
||||
|
13
crates/README.md
Normal file
13
crates/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Nushell core libraries and plugins
|
||||
|
||||
These sub-crates form both the foundation for Nu and a set of plugins which extend Nu with additional functionality.
|
||||
|
||||
Foundational libraries are split into two kinds of crates:
|
||||
|
||||
* Core crates - those crates that work together to build the Nushell language engine
|
||||
* Support crates - a set of crates that support the engine with additional features like JSON support, ANSI support, and more.
|
||||
|
||||
Plugins are likewise also split into two types:
|
||||
|
||||
* Core plugins - plugins that provide part of the default experience of Nu, including access to the system properties, processes, and web-connectivity features.
|
||||
* Extra plugins - these plugins run a wide range of different capabilities like working with different file types, charting, viewing binary data, and more.
|
36
crates/nu-cli/Cargo.toml
Normal file
36
crates/nu-cli/Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "CLI-related functionality for Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.66.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.66.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.66.2" }
|
||||
rstest = "0.15.0"
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.2" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.2" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
crossterm = "0.23.0"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
|
||||
chrono = "0.4.19"
|
||||
is_executable = "1.0.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
regex = "1.5.4"
|
||||
sysinfo = "0.24.1"
|
||||
|
||||
[features]
|
||||
plugin = []
|
21
crates/nu-cli/LICENSE
Normal file
21
crates/nu-cli/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
76
crates/nu-cli/src/commands.rs
Normal file
76
crates/nu-cli/src/commands.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use crate::util::report_error;
|
||||
use log::info;
|
||||
use miette::Result;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
};
|
||||
|
||||
/// Run a command (or commands) given to us by the user
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
table_mode: Option<Value>,
|
||||
) -> Result<Option<i64>> {
|
||||
// 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) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
engine_state.set_config(&config);
|
||||
}
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (output, err) = parse(&mut working_set, None, commands.item.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
// Update permanent state
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
// Run the block
|
||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
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)
|
||||
}
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
43
crates/nu-cli/src/completions/base.rs
Normal file
43
crates/nu-cli/src/completions/base.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use crate::completions::{CompletionOptions, SortBy};
|
||||
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
|
||||
use reedline::Suggestion;
|
||||
|
||||
// Completer trait represents the three stages of the completion
|
||||
// fetch, filter and sort
|
||||
pub trait Completer {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion>;
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::Ascending
|
||||
}
|
||||
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut filtered_items = items;
|
||||
|
||||
// Sort items
|
||||
match self.get_sort_by() {
|
||||
SortBy::LevenshteinDistance => {
|
||||
filtered_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
SortBy::Ascending => {
|
||||
filtered_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::None => {}
|
||||
};
|
||||
|
||||
filtered_items
|
||||
}
|
||||
}
|
230
crates/nu-cli/src/completions/command_completions.rs
Normal file
230
crates/nu-cli/src/completions/command_completions.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||
use nu_parser::FlatShape;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
}
|
||||
|
||||
impl CommandCompletion {
|
||||
pub fn new(
|
||||
engine_state: Arc<EngineState>,
|
||||
_: &StateWorkingSet,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
flattened,
|
||||
flat_shape,
|
||||
}
|
||||
}
|
||||
|
||||
fn external_command_completion(
|
||||
&self,
|
||||
prefix: &str,
|
||||
match_algorithm: MatchAlgorithm,
|
||||
) -> Vec<String> {
|
||||
let mut executables = vec![];
|
||||
|
||||
let paths = self.engine_state.get_env_var("PATH");
|
||||
|
||||
if let Some(paths) = paths {
|
||||
if let Ok(paths) = paths.as_list() {
|
||||
for path in paths {
|
||||
let path = path.as_string().unwrap_or_default();
|
||||
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if self.engine_state.config.max_external_completion_results
|
||||
> executables.len() as i64
|
||||
&& !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
&& matches!(
|
||||
item.path().file_name().map(|x| match_algorithm
|
||||
.matches_str(&x.to_string_lossy(), prefix)),
|
||||
Some(true)
|
||||
)
|
||||
&& is_executable::is_executable(&item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executables
|
||||
}
|
||||
|
||||
fn complete_commands(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
find_externals: bool,
|
||||
match_algorithm: MatchAlgorithm,
|
||||
) -> Vec<Suggestion> {
|
||||
let partial = working_set.get_span_contents(span);
|
||||
|
||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||
|
||||
let results = working_set
|
||||
.find_commands_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||
description: x.1,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let results_aliases = working_set
|
||||
.find_aliases_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: String::from_utf8_lossy(&x).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
let results = if find_externals {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
for external in results_external {
|
||||
if results.contains(&external) {
|
||||
results.push(Suggestion {
|
||||
value: format!("^{}", external.value),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: external.span,
|
||||
append_whitespace: true,
|
||||
})
|
||||
} else {
|
||||
results.push(external)
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
} else {
|
||||
results
|
||||
};
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for CommandCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let last = self
|
||||
.flattened
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|x| x.0.end > pos)
|
||||
.take_while(|x| {
|
||||
matches!(
|
||||
x.1,
|
||||
FlatShape::InternalCall
|
||||
| FlatShape::External
|
||||
| FlatShape::ExternalArg
|
||||
| FlatShape::Literal
|
||||
| FlatShape::String
|
||||
)
|
||||
})
|
||||
.last();
|
||||
|
||||
// The last item here would be the earliest shape that could possible by part of this subcommand
|
||||
let subcommands = if let Some(last) = last {
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
Span {
|
||||
start: last.0.start,
|
||||
end: pos,
|
||||
},
|
||||
offset,
|
||||
false,
|
||||
options.match_algorithm,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| ((span.end - span.start) == 0)
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
span,
|
||||
offset,
|
||||
config.enable_external_completion,
|
||||
options.match_algorithm,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
subcommands
|
||||
.into_iter()
|
||||
.chain(commands.into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
385
crates/nu-cli/src/completions/completer.rs
Normal file
385
crates/nu-cli/src/completions/completer.rs
Normal file
@ -0,0 +1,385 @@
|
||||
use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||
};
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuCompleter {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
}
|
||||
|
||||
impl NuCompleter {
|
||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
|
||||
// Process the completion for a given completer
|
||||
fn process_completion<T: Completer>(
|
||||
&self,
|
||||
completer: &mut T,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
new_span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let mut options = CompletionOptions {
|
||||
case_sensitive: config.case_sensitive_completions,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if config.completion_algorithm == "fuzzy" {
|
||||
options.match_algorithm = MatchAlgorithm::Fuzzy;
|
||||
}
|
||||
|
||||
// Fetch
|
||||
let mut suggestions =
|
||||
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
|
||||
|
||||
// Sort
|
||||
suggestions = completer.sort(suggestions, prefix);
|
||||
|
||||
suggestions
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
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();
|
||||
new_line.push(b'a');
|
||||
let pos = offset + pos;
|
||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
|
||||
// Create a new span
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
} else {
|
||||
Span {
|
||||
start: flat.0.start - span_offset,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source)
|
||||
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" {
|
||||
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(),
|
||||
);
|
||||
|
||||
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(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
|
||||
impl ReedlineCompleter for NuCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// and the depth (a.b.c)
|
||||
fn most_left_variable(
|
||||
idx: usize,
|
||||
working_set: &StateWorkingSet<'_>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
) -> Option<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
// Reverse items to read the list backwards and truncate
|
||||
// because the only items that matters are the ones before the current index
|
||||
let mut rev = flattened;
|
||||
rev.truncate(idx);
|
||||
rev = rev.into_iter().rev().collect();
|
||||
|
||||
// Store the variables and sub levels found and reverse to correct order
|
||||
let mut variables_found: Vec<Vec<u8>> = vec![];
|
||||
let mut found_var = false;
|
||||
for item in rev.clone() {
|
||||
let result = working_set.get_span_contents(item.0).to_vec();
|
||||
|
||||
match item.1 {
|
||||
FlatShape::Variable => {
|
||||
variables_found.push(result);
|
||||
found_var = true;
|
||||
|
||||
break;
|
||||
}
|
||||
FlatShape::String => {
|
||||
variables_found.push(result);
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If most left var was not found
|
||||
if !found_var {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reverse the order back
|
||||
variables_found = variables_found.into_iter().rev().collect();
|
||||
|
||||
// Extract the variable and the sublevels
|
||||
let var = variables_found.first().unwrap_or(&vec![]).to_vec();
|
||||
let sublevels: Vec<Vec<u8>> = variables_found.into_iter().skip(1).collect();
|
||||
|
||||
Some((var, sublevels))
|
||||
}
|
137
crates/nu-cli/src/completions/completion_options.rs
Normal file
137
crates/nu-cli/src/completions/completion_options.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_parser::trim_quotes_str;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SortBy {
|
||||
LevenshteinDistance,
|
||||
Ascending,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Describes how suggestions should be matched.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum MatchAlgorithm {
|
||||
/// Only show suggestions which begin with the given input
|
||||
///
|
||||
/// Example:
|
||||
/// "git switch" is matched by "git sw"
|
||||
Prefix,
|
||||
|
||||
/// Only show suggestions which contain the input chars at any place
|
||||
///
|
||||
/// Example:
|
||||
/// "git checkout" is matched by "gco"
|
||||
Fuzzy,
|
||||
}
|
||||
|
||||
impl MatchAlgorithm {
|
||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
||||
let haystack = trim_quotes_str(haystack);
|
||||
let needle = trim_quotes_str(needle);
|
||||
match *self {
|
||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||
MatchAlgorithm::Fuzzy => {
|
||||
let matcher = SkimMatcherV2::default();
|
||||
matcher.fuzzy_match(haystack, needle).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||
pub fn matches_u8(&self, haystack: &[u8], needle: &[u8]) -> bool {
|
||||
match *self {
|
||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||
MatchAlgorithm::Fuzzy => {
|
||||
let haystack_str = String::from_utf8_lossy(haystack);
|
||||
let needle_str = String::from_utf8_lossy(needle);
|
||||
|
||||
let matcher = SkimMatcherV2::default();
|
||||
matcher.fuzzy_match(&haystack_str, &needle_str).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for MatchAlgorithm {
|
||||
type Error = InvalidMatchAlgorithm;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.as_str() {
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
"fuzzy" => Ok(Self::Fuzzy),
|
||||
_ => Err(InvalidMatchAlgorithm::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InvalidMatchAlgorithm {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Display for InvalidMatchAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
InvalidMatchAlgorithm::Unknown => write!(f, "unknown match algorithm"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidMatchAlgorithm {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionOptions {
|
||||
pub case_sensitive: bool,
|
||||
pub positional: bool,
|
||||
pub sort_by: SortBy,
|
||||
pub match_algorithm: MatchAlgorithm,
|
||||
}
|
||||
|
||||
impl Default for CompletionOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
case_sensitive: true,
|
||||
positional: true,
|
||||
sort_by: SortBy::Ascending,
|
||||
match_algorithm: MatchAlgorithm::Prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::MatchAlgorithm;
|
||||
|
||||
#[test]
|
||||
fn match_algorithm_prefix() {
|
||||
let algorithm = MatchAlgorithm::Prefix;
|
||||
|
||||
assert!(algorithm.matches_str("example text", ""));
|
||||
assert!(algorithm.matches_str("example text", "examp"));
|
||||
assert!(!algorithm.matches_str("example text", "text"));
|
||||
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_algorithm_fuzzy() {
|
||||
let algorithm = MatchAlgorithm::Fuzzy;
|
||||
|
||||
assert!(algorithm.matches_str("example text", ""));
|
||||
assert!(algorithm.matches_str("example text", "examp"));
|
||||
assert!(algorithm.matches_str("example text", "ext"));
|
||||
assert!(algorithm.matches_str("example text", "mplxt"));
|
||||
assert!(!algorithm.matches_str("example text", "mpp"));
|
||||
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 3]));
|
||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 2]));
|
||||
}
|
||||
}
|
233
crates/nu-cli/src/completions/custom_completions.rs
Normal file
233
crates/nu-cli/src/completions/custom_completions.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||
use nu_engine::eval_call;
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, Expression},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, Span, Type, Value,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
decl_id: usize,
|
||||
line: String,
|
||||
sort_by: SortBy,
|
||||
}
|
||||
|
||||
impl CustomCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
decl_id,
|
||||
line,
|
||||
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 {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
completion_options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
// Line position
|
||||
let line_pos = pos - offset;
|
||||
|
||||
// Call custom declaration
|
||||
let result = eval_call(
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
&Call {
|
||||
decl_id: self.decl_id,
|
||||
head: span,
|
||||
arguments: vec![
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
ty: Type::String,
|
||||
expr: Expr::String(self.line.clone()),
|
||||
custom_completion: None,
|
||||
}),
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
ty: Type::Int,
|
||||
expr: Expr::Int(line_pos as i64),
|
||||
custom_completion: None,
|
||||
}),
|
||||
],
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: true,
|
||||
},
|
||||
PipelineData::new(span),
|
||||
);
|
||||
|
||||
let mut custom_completion_options = None;
|
||||
|
||||
// Parse result
|
||||
let suggestions = match result {
|
||||
Ok(pd) => {
|
||||
let value = pd.into_value(span);
|
||||
match &value {
|
||||
Value::Record { .. } => {
|
||||
let completions = value
|
||||
.get_data_by_key("completions")
|
||||
.and_then(|val| {
|
||||
val.as_list()
|
||||
.ok()
|
||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
|
||||
if let Some(Value::Record { .. }) = &options {
|
||||
let options = options.unwrap_or_default();
|
||||
let should_sort = options
|
||||
.get_data_by_key("sort")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_sort {
|
||||
self.sort_by = SortBy::Ascending;
|
||||
}
|
||||
|
||||
custom_completion_options = Some(CompletionOptions {
|
||||
case_sensitive: options
|
||||
.get_data_by_key("case_sensitive")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(true),
|
||||
positional: options
|
||||
.get_data_by_key("positional")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(true),
|
||||
sort_by: if should_sort {
|
||||
SortBy::Ascending
|
||||
} else {
|
||||
SortBy::None
|
||||
},
|
||||
match_algorithm: match options
|
||||
.get_data_by_key("completion_algorithm")
|
||||
{
|
||||
Some(option) => option
|
||||
.as_string()
|
||||
.ok()
|
||||
.and_then(|option| option.try_into().ok())
|
||||
.unwrap_or(MatchAlgorithm::Prefix),
|
||||
None => completion_options.match_algorithm,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
completions
|
||||
}
|
||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
} else {
|
||||
filter(&prefix, suggestions, completion_options)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
self.sort_by
|
||||
}
|
||||
}
|
||||
|
||||
fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) -> Vec<Suggestion> {
|
||||
items
|
||||
.into_iter()
|
||||
.filter(|it| match options.match_algorithm {
|
||||
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||
(false, positional) => {
|
||||
let value = it.value.to_lowercase();
|
||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_lowercase();
|
||||
if positional {
|
||||
value.starts_with(&prefix)
|
||||
} else {
|
||||
value.contains(&prefix)
|
||||
}
|
||||
}
|
||||
},
|
||||
MatchAlgorithm::Fuzzy => options
|
||||
.match_algorithm
|
||||
.matches_u8(it.value.as_bytes(), prefix),
|
||||
})
|
||||
.collect()
|
||||
}
|
160
crates/nu-cli/src/completions/directory_completions.rs
Normal file
160
crates/nu-cli/src/completions/directory_completions.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use crate::completions::{matches, Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{partial_from, prepend_base_dir};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirectoryCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl DirectoryCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DirectoryCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Filter only the folders
|
||||
let output: Vec<_> = directory_completion(span, &partial, &cwd, options)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
if value.starts_with('.') {
|
||||
hidden.push(item);
|
||||
} else {
|
||||
non_hidden.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append the hidden folders to the non hidden vec to avoid creating a new vec
|
||||
non_hidden.append(&mut hidden);
|
||||
|
||||
non_hidden
|
||||
}
|
||||
}
|
||||
|
||||
pub fn directory_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
let original_input = partial;
|
||||
|
||||
let (base_dir_name, partial) = partial_from(partial);
|
||||
|
||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
||||
|
||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||
if base_dir == Path::new("") {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
return result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
if let Ok(metadata) = fs::metadata(entry.path()) {
|
||||
if metadata.is_dir() {
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
|
||||
if entry.path().is_dir() {
|
||||
path.push(SEP);
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
}
|
124
crates/nu-cli/src/completions/dotnu_completions.rs
Normal file
124
crates/nu-cli/src/completions/dotnu_completions.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use crate::completions::{
|
||||
file_path_completion, partial_from, Completer, CompletionOptions, SortBy,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DotNuCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl DotNuCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DotNuCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut search_dirs: Vec<String> = vec![];
|
||||
let (base_dir, mut partial) = partial_from(&prefix_str);
|
||||
let mut is_current_folder = false;
|
||||
|
||||
// Fetch the lib dirs
|
||||
let lib_dirs: Vec<String> =
|
||||
if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") {
|
||||
lib_dirs
|
||||
.as_list()
|
||||
.into_iter()
|
||||
.flat_map(|it| {
|
||||
it.iter().map(|x| {
|
||||
x.as_path()
|
||||
.expect("internal error: failed to convert lib path")
|
||||
})
|
||||
})
|
||||
.map(|it| {
|
||||
it.into_os_string()
|
||||
.into_string()
|
||||
.expect("internal error: failed to convert OS path")
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Check if the base_dir is a folder
|
||||
if base_dir != format!(".{}", SEP) {
|
||||
// Add the base dir into the directories to be searched
|
||||
search_dirs.push(base_dir.clone());
|
||||
|
||||
// Reset the partial adding the basic dir back
|
||||
// in order to make the span replace work properly
|
||||
let mut base_dir_partial = base_dir;
|
||||
base_dir_partial.push_str(&partial);
|
||||
|
||||
partial = base_dir_partial;
|
||||
} else {
|
||||
// Fetch the current folder
|
||||
let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
is_current_folder = true;
|
||||
|
||||
// Add the current folder and the lib dirs into the
|
||||
// directories to be searched
|
||||
search_dirs.push(current_folder);
|
||||
search_dirs.extend(lib_dirs);
|
||||
}
|
||||
|
||||
// Fetch the files filtering the ones that ends with .nu
|
||||
// and transform them into suggestions
|
||||
let output: Vec<Suggestion> = search_dirs
|
||||
.into_iter()
|
||||
.flat_map(|it| {
|
||||
file_path_completion(span, &partial, &it, options)
|
||||
.into_iter()
|
||||
.filter(|it| {
|
||||
// Different base dir, so we list the .nu files or folders
|
||||
if !is_current_folder {
|
||||
it.1.ends_with(".nu") || it.1.ends_with(SEP)
|
||||
} else {
|
||||
// Lib dirs, so we filter only the .nu files
|
||||
it.1.ends_with(".nu")
|
||||
}
|
||||
})
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
190
crates/nu-cli/src/completions/file_completions.rs
Normal file
190
crates/nu-cli/src/completions/file_completions.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path};
|
||||
use std::sync::Arc;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl FileCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for FileCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
if value.starts_with('.') {
|
||||
hidden.push(item);
|
||||
} else {
|
||||
non_hidden.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append the hidden folders to the non hidden vec to avoid creating a new vec
|
||||
non_hidden.append(&mut hidden);
|
||||
|
||||
non_hidden
|
||||
}
|
||||
}
|
||||
|
||||
pub fn partial_from(input: &str) -> (String, String) {
|
||||
let partial = input.replace('`', "");
|
||||
|
||||
// If partial is only a word we want to search in the current dir
|
||||
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", &partial));
|
||||
// On windows, this standardizes paths to use \
|
||||
let mut base = base.replace(is_separator, &SEP.to_string());
|
||||
|
||||
// rsplit_once removes the separator
|
||||
base.push(SEP);
|
||||
|
||||
(base.to_string(), rest.to_string())
|
||||
}
|
||||
|
||||
pub fn file_path_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
let original_input = partial;
|
||||
let (base_dir_name, partial) = partial_from(partial);
|
||||
|
||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||
if base_dir == Path::new("") {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
return result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
|
||||
if entry.path().is_dir() {
|
||||
path.push(SEP);
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||
// Check for case sensitive
|
||||
if !options.case_sensitive {
|
||||
return options
|
||||
.match_algorithm
|
||||
.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase());
|
||||
}
|
||||
|
||||
options.match_algorithm.matches_str(from, partial)
|
||||
}
|
||||
|
||||
/// Returns whether the base_dir should be prepended to the file path
|
||||
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
||||
if base_dir == format!(".{}", SEP) {
|
||||
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
||||
// input already includes a local folder prefix.
|
||||
let manually_entered = {
|
||||
let mut chars = input.chars();
|
||||
let first_char = chars.next();
|
||||
let second_char = chars.next();
|
||||
|
||||
first_char == Some('.') && second_char.map(is_separator).unwrap_or(false)
|
||||
};
|
||||
|
||||
manually_entered
|
||||
} else {
|
||||
// always prepend the base dir if it is a subfolder
|
||||
true
|
||||
}
|
||||
}
|
86
crates/nu-cli/src/completions/flag_completions.rs
Normal file
86
crates/nu-cli/src/completions/flag_completions.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::StateWorkingSet,
|
||||
Span,
|
||||
};
|
||||
|
||||
use reedline::Suggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlagCompletion {
|
||||
expression: Expression,
|
||||
}
|
||||
|
||||
impl FlagCompletion {
|
||||
pub fn new(expression: Expression) -> Self {
|
||||
Self { expression }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for FlagCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
// Check if it's a flag
|
||||
if let Expr::Call(call) = &self.expression.expr {
|
||||
let decl = working_set.get_decl(call.decl_id);
|
||||
let sig = decl.signature();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for named in &sig.named {
|
||||
let flag_desc = &named.desc;
|
||||
if let Some(short) = named.short {
|
||||
let mut named = vec![0; short.len_utf8()];
|
||||
short.encode_utf8(&mut named);
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if named.long.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut named = named.long.as_bytes().to_vec();
|
||||
named.insert(0, b'-');
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
23
crates/nu-cli/src/completions/mod.rs
Normal file
23
crates/nu-cli/src/completions/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
mod base;
|
||||
mod command_completions;
|
||||
mod completer;
|
||||
mod completion_options;
|
||||
mod custom_completions;
|
||||
mod directory_completions;
|
||||
mod dotnu_completions;
|
||||
mod file_completions;
|
||||
mod flag_completions;
|
||||
mod variable_completions;
|
||||
|
||||
pub use base::Completer;
|
||||
pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||
pub use custom_completions::CustomCompletion;
|
||||
pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
pub use file_completions::{
|
||||
file_path_completion, matches, partial_from, prepend_base_dir, FileCompletion,
|
||||
};
|
||||
pub use flag_completions::FlagCompletion;
|
||||
pub use variable_completions::VariableCompletion;
|
292
crates/nu-cli/src/completions/variable_completions.rs
Normal file
292
crates/nu-cli/src/completions/variable_completions.rs
Normal file
@ -0,0 +1,292 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_engine::eval_variable;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span, Value,
|
||||
};
|
||||
|
||||
use reedline::Suggestion;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||
stack: Stack,
|
||||
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
|
||||
}
|
||||
|
||||
impl VariableCompletion {
|
||||
pub fn new(
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
var_context: (Vec<u8>, Vec<Vec<u8>>),
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
var_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for VariableCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output = vec![];
|
||||
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
let var_id = working_set.find_variable(&self.var_context.0);
|
||||
let current_span = reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
};
|
||||
let sublevels_count = self.var_context.1.len();
|
||||
|
||||
// Completions for the given variable
|
||||
if !var_str.is_empty() {
|
||||
// Completion for $env.<tab>
|
||||
if var_str.as_str() == "$env" {
|
||||
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
||||
|
||||
// Return nested values
|
||||
if sublevels_count > 0 {
|
||||
// Extract the target var ($env.<target-var>)
|
||||
let target_var = self.var_context.1[0].clone();
|
||||
let target_var_str =
|
||||
str::from_utf8(&target_var).unwrap_or_default().to_string();
|
||||
|
||||
// Everything after the target var is the nested level ($env.<target-var>.<nested_levels>...)
|
||||
let nested_levels: Vec<Vec<u8>> =
|
||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
||||
|
||||
if let Some(val) = env_vars.get(&target_var_str) {
|
||||
for suggestion in
|
||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} else {
|
||||
// No nesting provided, return all env vars
|
||||
for env_var in env_vars {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(env_var.0.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// Completions for $nu.<tab>
|
||||
if var_str.as_str() == "$nu" {
|
||||
// Eval nu var
|
||||
if let Ok(nuval) = eval_variable(
|
||||
&self.engine_state,
|
||||
&self.stack,
|
||||
nu_protocol::NU_VARIABLE_ID,
|
||||
nu_protocol::Span {
|
||||
start: current_span.start,
|
||||
end: current_span.end,
|
||||
},
|
||||
) {
|
||||
for suggestion in
|
||||
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// Completion other variable types
|
||||
if let Some(var_id) = var_id {
|
||||
// Extract the variable value from the stack
|
||||
let var = self.stack.get_var(
|
||||
var_id,
|
||||
Span {
|
||||
start: span.start,
|
||||
end: span.end,
|
||||
},
|
||||
);
|
||||
|
||||
// If the value exists and it's of type Record
|
||||
if let Ok(value) = var {
|
||||
for suggestion in
|
||||
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Variable completion (e.g: $en<tab> to complete $env)
|
||||
for builtin in builtins {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(builtin.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
||||
// command_completions).
|
||||
let mut removed_overlays = vec![];
|
||||
// Working set scope vars
|
||||
for scope_frame in working_set.delta.scope.iter().rev() {
|
||||
for overlay_frame in scope_frame
|
||||
.active_overlays(&mut removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Permanent state vars
|
||||
// for scope in &self.engine_state.scope {
|
||||
for overlay_frame in self
|
||||
.engine_state
|
||||
.active_overlays(&removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// Find recursively the values for sublevels
|
||||
// if no sublevels are set it returns the current value
|
||||
fn nested_suggestions(
|
||||
val: Value,
|
||||
sublevels: Vec<Vec<u8>>,
|
||||
current_span: reedline::Span,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output: Vec<Suggestion> = vec![];
|
||||
let value = recursive_value(val, sublevels);
|
||||
|
||||
match value {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: _,
|
||||
span: _,
|
||||
} => {
|
||||
// Add all the columns as completion
|
||||
for item in cols {
|
||||
output.push(Suggestion {
|
||||
value: item,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
_ => output,
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the recursive value (e.g: $var.a.b.c)
|
||||
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
// Go to next sublevel
|
||||
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: _,
|
||||
} => {
|
||||
for item in cols.into_iter().zip(vals.into_iter()) {
|
||||
// Check if index matches with sublevel
|
||||
if item.0.as_bytes().to_vec() == next_sublevel {
|
||||
// If matches try to fetch recursively the next
|
||||
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
|
||||
}
|
||||
}
|
||||
|
||||
// Current sublevel value not found
|
||||
return Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
};
|
||||
}
|
||||
_ => return val,
|
||||
}
|
||||
}
|
||||
|
||||
val
|
||||
}
|
120
crates/nu-cli/src/config_files.rs
Normal file
120
crates/nu-cli/src/config_files.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
#[cfg(feature = "plugin")]
|
||||
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, Span};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
const PLUGIN_FILE: &str = "plugin.nu";
|
||||
|
||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
// Reading signatures from signature file
|
||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||
|
||||
let plugin_path = engine_state.plugin_signatures.clone();
|
||||
if let Some(plugin_path) = plugin_path {
|
||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
||||
|
||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
&plugin_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_file(
|
||||
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
|
||||
plugin_path.push(storage_path);
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
engine_state.plugin_signatures = Some(plugin_path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_config_contents(
|
||||
config_path: PathBuf,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) {
|
||||
if config_path.exists() & config_path.is_file() {
|
||||
let config_filename = config_path.to_string_lossy().to_owned();
|
||||
|
||||
if let Ok(contents) = std::fs::read(&config_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
&config_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
|
||||
// Merge the environment 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_env(stack, cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
139
crates/nu-cli/src/eval_file.rs
Normal file
139
crates/nu-cli/src/eval_file.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_engine::convert_env_values;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::Type;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
|
||||
/// Main function used when a file path is found as argument for nu
|
||||
pub fn evaluate_file(
|
||||
path: String,
|
||||
args: &[String],
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
// 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);
|
||||
}
|
||||
|
||||
let file = std::fs::read(&path).into_diagnostic()?;
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
trace!("parsing file: {}", path);
|
||||
|
||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
||||
|
||||
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||
let args = format!("main {}", args.join(" "));
|
||||
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&file,
|
||||
&path,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_table_or_error(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
config: &mut Config,
|
||||
) -> Option<i64> {
|
||||
let exit_code = match &mut pipeline_data {
|
||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Change the engine_state config to use the passed in configuration
|
||||
engine_state.set_config(config);
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
Some(decl_id) => {
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
} else {
|
||||
let table = command.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
print_or_exit(table, engine_state, config);
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
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));
|
||||
}
|
||||
}
|
33
crates/nu-cli/src/lib.rs
Normal file
33
crates/nu-cli/src/lib.rs
Normal file
@ -0,0 +1,33 @@
|
||||
mod commands;
|
||||
mod completions;
|
||||
mod config_files;
|
||||
mod eval_file;
|
||||
mod menus;
|
||||
mod nu_highlight;
|
||||
mod print;
|
||||
mod prompt;
|
||||
mod prompt_update;
|
||||
mod reedline_config;
|
||||
mod repl;
|
||||
mod syntax_highlight;
|
||||
mod util;
|
||||
mod validation;
|
||||
|
||||
pub use commands::evaluate_commands;
|
||||
pub use completions::{FileCompletion, NuCompleter};
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use repl::{eval_env_change_hook, eval_hook};
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::add_plugin_file;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::read_plugin_file;
|
724
crates/nu-cli/src/menus/description_menu.rs
Normal file
724
crates/nu-cli/src/menus/description_menu.rs
Normal file
@ -0,0 +1,724 @@
|
||||
use {
|
||||
nu_ansi_term::{ansi::RESET, Style},
|
||||
reedline::{
|
||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion,
|
||||
},
|
||||
};
|
||||
|
||||
/// Default values used as reference for the menu. These values are set during
|
||||
/// the initial declaration of the menu and are always kept as reference for the
|
||||
/// changeable [`WorkingDetails`]
|
||||
struct DefaultMenuDetails {
|
||||
/// Number of columns that the menu will have
|
||||
pub columns: u16,
|
||||
/// Column width
|
||||
pub col_width: Option<usize>,
|
||||
/// Column padding
|
||||
pub col_padding: usize,
|
||||
/// Number of rows for commands
|
||||
pub selection_rows: u16,
|
||||
/// Number of rows allowed to display the description
|
||||
pub description_rows: usize,
|
||||
}
|
||||
|
||||
impl Default for DefaultMenuDetails {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
columns: 4,
|
||||
col_width: None,
|
||||
col_padding: 2,
|
||||
selection_rows: 4,
|
||||
description_rows: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the actual column conditions of the menu. These conditions change
|
||||
/// since they need to accommodate possible different line sizes for the column values
|
||||
#[derive(Default)]
|
||||
struct WorkingDetails {
|
||||
/// Number of columns that the menu will have
|
||||
pub columns: u16,
|
||||
/// Column width
|
||||
pub col_width: usize,
|
||||
/// Number of rows for description
|
||||
pub description_rows: usize,
|
||||
}
|
||||
|
||||
/// Completion menu definition
|
||||
pub struct DescriptionMenu {
|
||||
/// Menu name
|
||||
name: String,
|
||||
/// Menu status
|
||||
active: bool,
|
||||
/// Menu coloring
|
||||
color: MenuTextStyle,
|
||||
/// Default column details that are set when creating the menu
|
||||
/// These values are the reference for the working details
|
||||
default_details: DefaultMenuDetails,
|
||||
/// Number of minimum rows that are displayed when
|
||||
/// the required lines is larger than the available lines
|
||||
min_rows: u16,
|
||||
/// Working column details keep changing based on the collected values
|
||||
working_details: WorkingDetails,
|
||||
/// Menu cached values
|
||||
values: Vec<Suggestion>,
|
||||
/// column position of the cursor. Starts from 0
|
||||
col_pos: u16,
|
||||
/// row position in the menu. Starts from 0
|
||||
row_pos: u16,
|
||||
/// Menu marker when active
|
||||
marker: String,
|
||||
/// Event sent to the menu
|
||||
event: Option<MenuEvent>,
|
||||
/// String collected after the menu is activated
|
||||
input: Option<String>,
|
||||
/// Examples to select
|
||||
examples: Vec<String>,
|
||||
/// Example index
|
||||
example_index: Option<usize>,
|
||||
/// Examples may not be shown if there is not enough space in the screen
|
||||
show_examples: bool,
|
||||
/// Skipped description rows
|
||||
skipped_rows: usize,
|
||||
/// Calls the completer using only the line buffer difference difference
|
||||
/// after the menu was activated
|
||||
only_buffer_difference: bool,
|
||||
}
|
||||
|
||||
impl Default for DescriptionMenu {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "description_menu".to_string(),
|
||||
active: false,
|
||||
color: MenuTextStyle::default(),
|
||||
default_details: DefaultMenuDetails::default(),
|
||||
min_rows: 3,
|
||||
working_details: WorkingDetails::default(),
|
||||
values: Vec::new(),
|
||||
col_pos: 0,
|
||||
row_pos: 0,
|
||||
marker: "? ".to_string(),
|
||||
event: None,
|
||||
input: None,
|
||||
examples: Vec::new(),
|
||||
example_index: None,
|
||||
show_examples: true,
|
||||
skipped_rows: 0,
|
||||
only_buffer_difference: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Menu configuration
|
||||
impl DescriptionMenu {
|
||||
/// Menu builder with new name
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_text_style(mut self, text_style: Style) -> Self {
|
||||
self.color.text_style = text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
|
||||
self.color.selected_text_style = selected_text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
|
||||
self.color.description_style = description_text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new columns value
|
||||
pub fn with_columns(mut self, columns: u16) -> Self {
|
||||
self.default_details.columns = columns;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new column width value
|
||||
pub fn with_column_width(mut self, col_width: Option<usize>) -> Self {
|
||||
self.default_details.col_width = col_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new column width value
|
||||
pub fn with_column_padding(mut self, col_padding: usize) -> Self {
|
||||
self.default_details.col_padding = col_padding;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new selection rows value
|
||||
pub fn with_selection_rows(mut self, selection_rows: u16) -> Self {
|
||||
self.default_details.selection_rows = selection_rows;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new description rows value
|
||||
pub fn with_description_rows(mut self, description_rows: usize) -> Self {
|
||||
self.default_details.description_rows = description_rows;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with marker
|
||||
pub fn with_marker(mut self, marker: String) -> Self {
|
||||
self.marker = marker;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new only buffer difference
|
||||
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
|
||||
self.only_buffer_difference = only_buffer_difference;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Menu functionality
|
||||
impl DescriptionMenu {
|
||||
/// Move menu cursor to the next element
|
||||
fn move_next(&mut self) {
|
||||
let mut new_col = self.col_pos + 1;
|
||||
let mut new_row = self.row_pos;
|
||||
|
||||
if new_col >= self.get_cols() {
|
||||
new_row += 1;
|
||||
new_col = 0;
|
||||
}
|
||||
|
||||
if new_row >= self.get_rows() {
|
||||
new_row = 0;
|
||||
new_col = 0;
|
||||
}
|
||||
|
||||
let position = new_row * self.get_cols() + new_col;
|
||||
if position >= self.get_values().len() as u16 {
|
||||
self.reset_position();
|
||||
} else {
|
||||
self.col_pos = new_col;
|
||||
self.row_pos = new_row;
|
||||
}
|
||||
}
|
||||
|
||||
/// Move menu cursor to the previous element
|
||||
fn move_previous(&mut self) {
|
||||
let new_col = self.col_pos.checked_sub(1);
|
||||
|
||||
let (new_col, new_row) = match new_col {
|
||||
Some(col) => (col, self.row_pos),
|
||||
None => match self.row_pos.checked_sub(1) {
|
||||
Some(row) => (self.get_cols().saturating_sub(1), row),
|
||||
None => (
|
||||
self.get_cols().saturating_sub(1),
|
||||
self.get_rows().saturating_sub(1),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
let position = new_row * self.get_cols() + new_col;
|
||||
if position >= self.get_values().len() as u16 {
|
||||
self.col_pos = (self.get_values().len() as u16 % self.get_cols()).saturating_sub(1);
|
||||
self.row_pos = self.get_rows().saturating_sub(1);
|
||||
} else {
|
||||
self.col_pos = new_col;
|
||||
self.row_pos = new_row;
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu index based on column and row position
|
||||
fn index(&self) -> usize {
|
||||
let index = self.row_pos * self.get_cols() + self.col_pos;
|
||||
index as usize
|
||||
}
|
||||
|
||||
/// Get selected value from the menu
|
||||
fn get_value(&self) -> Option<Suggestion> {
|
||||
self.get_values().get(self.index()).cloned()
|
||||
}
|
||||
|
||||
/// Calculates how many rows the Menu will use
|
||||
fn get_rows(&self) -> u16 {
|
||||
let values = self.get_values().len() as u16;
|
||||
|
||||
if values == 0 {
|
||||
// When the values are empty the no_records_msg is shown, taking 1 line
|
||||
return 1;
|
||||
}
|
||||
|
||||
let rows = values / self.get_cols();
|
||||
if values % self.get_cols() != 0 {
|
||||
rows + 1
|
||||
} else {
|
||||
rows
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns working details col width
|
||||
fn get_width(&self) -> usize {
|
||||
self.working_details.col_width
|
||||
}
|
||||
|
||||
/// Reset menu position
|
||||
fn reset_position(&mut self) {
|
||||
self.col_pos = 0;
|
||||
self.row_pos = 0;
|
||||
self.skipped_rows = 0;
|
||||
}
|
||||
|
||||
fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
|
||||
let msg = "TYPE TO START SEARCH";
|
||||
if use_ansi_coloring {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.color.selected_text_style.prefix(),
|
||||
msg,
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
msg.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns working details columns
|
||||
fn get_cols(&self) -> u16 {
|
||||
self.working_details.columns.max(1)
|
||||
}
|
||||
|
||||
/// End of line for menu
|
||||
fn end_of_line(&self, column: u16, index: usize) -> &str {
|
||||
let is_last = index == self.values.len().saturating_sub(1);
|
||||
if column == self.get_cols().saturating_sub(1) || is_last {
|
||||
"\r\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Update list of examples from the actual value
|
||||
fn update_examples(&mut self) {
|
||||
self.examples = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.extra)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.example_index = None;
|
||||
}
|
||||
|
||||
/// Creates default string that represents one suggestion from the menu
|
||||
fn create_entry_string(
|
||||
&self,
|
||||
suggestion: &Suggestion,
|
||||
index: usize,
|
||||
column: u16,
|
||||
empty_space: usize,
|
||||
use_ansi_coloring: bool,
|
||||
) -> String {
|
||||
if use_ansi_coloring {
|
||||
if index == self.index() {
|
||||
format!(
|
||||
"{}{}{}{:>empty$}{}",
|
||||
self.color.selected_text_style.prefix(),
|
||||
&suggestion.value,
|
||||
RESET,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}{}{:>empty$}{}",
|
||||
self.color.text_style.prefix(),
|
||||
&suggestion.value,
|
||||
RESET,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If no ansi coloring is found, then the selection word is
|
||||
// the line in uppercase
|
||||
let (marker, empty_space) = if index == self.index() {
|
||||
(">", empty_space.saturating_sub(1))
|
||||
} else {
|
||||
("", empty_space)
|
||||
};
|
||||
|
||||
let line = format!(
|
||||
"{}{}{:>empty$}{}",
|
||||
marker,
|
||||
&suggestion.value,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
);
|
||||
|
||||
if index == self.index() {
|
||||
line.to_uppercase()
|
||||
} else {
|
||||
line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Description string with color
|
||||
fn create_description_string(&self, use_ansi_coloring: bool) -> String {
|
||||
let description = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.lines()
|
||||
.skip(self.skipped_rows)
|
||||
.take(self.working_details.description_rows)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\r\n");
|
||||
|
||||
if use_ansi_coloring && !description.is_empty() {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.color.description_style.prefix(),
|
||||
description,
|
||||
RESET,
|
||||
)
|
||||
} else {
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
/// Selectable list of examples from the actual value
|
||||
fn create_example_string(&self, use_ansi_coloring: bool) -> String {
|
||||
if !self.show_examples {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let examples: String = self
|
||||
.examples
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, example)| {
|
||||
if let Some(example_index) = self.example_index {
|
||||
if index == example_index {
|
||||
format!(
|
||||
" {}{}{}\r\n",
|
||||
self.color.selected_text_style.prefix(),
|
||||
example,
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
}
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if examples.is_empty() {
|
||||
"".into()
|
||||
} else if use_ansi_coloring {
|
||||
format!(
|
||||
"{}\r\n\r\nExamples:\r\n{}{}",
|
||||
self.color.description_style.prefix(),
|
||||
RESET,
|
||||
examples,
|
||||
)
|
||||
} else {
|
||||
format!("\r\n\r\nExamples:\r\n{}", examples,)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu for DescriptionMenu {
|
||||
/// Menu name
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
/// Menu indicator
|
||||
fn indicator(&self) -> &str {
|
||||
self.marker.as_str()
|
||||
}
|
||||
|
||||
/// Deactivates context menu
|
||||
fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
/// The menu stays active even with one record
|
||||
fn can_quick_complete(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The menu does not need to partially complete
|
||||
fn can_partially_complete(
|
||||
&mut self,
|
||||
_values_updated: bool,
|
||||
_line_buffer: &mut LineBuffer,
|
||||
_completer: &mut dyn Completer,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Selects what type of event happened with the menu
|
||||
fn menu_event(&mut self, event: MenuEvent) {
|
||||
match &event {
|
||||
MenuEvent::Activate(_) => self.active = true,
|
||||
MenuEvent::Deactivate => {
|
||||
self.active = false;
|
||||
self.input = None;
|
||||
self.values = Vec::new();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
self.event = Some(event);
|
||||
}
|
||||
|
||||
/// Updates menu values
|
||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
||||
if self.only_buffer_difference {
|
||||
if let Some(old_string) = &self.input {
|
||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
||||
if !input.is_empty() {
|
||||
self.reset_position();
|
||||
self.values = completer.complete(input, start);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
||||
self.values =
|
||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
||||
self.reset_position();
|
||||
}
|
||||
}
|
||||
|
||||
/// The working details for the menu changes based on the size of the lines
|
||||
/// collected from the completer
|
||||
fn update_working_details(
|
||||
&mut self,
|
||||
line_buffer: &mut LineBuffer,
|
||||
completer: &mut dyn Completer,
|
||||
painter: &Painter,
|
||||
) {
|
||||
if let Some(event) = self.event.take() {
|
||||
// Updating all working parameters from the menu before executing any of the
|
||||
// possible event
|
||||
let max_width = self.get_values().iter().fold(0, |acc, suggestion| {
|
||||
let str_len = suggestion.value.len() + self.default_details.col_padding;
|
||||
if str_len > acc {
|
||||
str_len
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
|
||||
// If no default width is found, then the total screen width is used to estimate
|
||||
// the column width based on the default number of columns
|
||||
let default_width = if let Some(col_width) = self.default_details.col_width {
|
||||
col_width
|
||||
} else {
|
||||
let col_width = painter.screen_width() / self.default_details.columns;
|
||||
col_width as usize
|
||||
};
|
||||
|
||||
// Adjusting the working width of the column based the max line width found
|
||||
// in the menu values
|
||||
if max_width > default_width {
|
||||
self.working_details.col_width = max_width;
|
||||
} else {
|
||||
self.working_details.col_width = default_width;
|
||||
};
|
||||
|
||||
// The working columns is adjusted based on possible number of columns
|
||||
// that could be fitted in the screen with the calculated column width
|
||||
let possible_cols = painter.screen_width() / self.working_details.col_width as u16;
|
||||
if possible_cols > self.default_details.columns {
|
||||
self.working_details.columns = self.default_details.columns.max(1);
|
||||
} else {
|
||||
self.working_details.columns = possible_cols;
|
||||
}
|
||||
|
||||
// Updating the working rows to display the description
|
||||
if self.menu_required_lines(painter.screen_width()) <= painter.remaining_lines() {
|
||||
self.working_details.description_rows = self.default_details.description_rows;
|
||||
self.show_examples = true;
|
||||
} else {
|
||||
self.working_details.description_rows = painter
|
||||
.remaining_lines()
|
||||
.saturating_sub(self.default_details.selection_rows + 1)
|
||||
as usize;
|
||||
|
||||
self.show_examples = false;
|
||||
}
|
||||
|
||||
match event {
|
||||
MenuEvent::Activate(_) => {
|
||||
self.reset_position();
|
||||
self.input = Some(line_buffer.get_buffer().to_string());
|
||||
self.update_values(line_buffer, completer);
|
||||
}
|
||||
MenuEvent::Deactivate => self.active = false,
|
||||
MenuEvent::Edit(_) => {
|
||||
self.reset_position();
|
||||
self.update_values(line_buffer, completer);
|
||||
self.update_examples()
|
||||
}
|
||||
MenuEvent::NextElement => {
|
||||
self.skipped_rows = 0;
|
||||
self.move_next();
|
||||
self.update_examples();
|
||||
}
|
||||
MenuEvent::PreviousElement => {
|
||||
self.skipped_rows = 0;
|
||||
self.move_previous();
|
||||
self.update_examples();
|
||||
}
|
||||
MenuEvent::MoveUp => {
|
||||
if let Some(example_index) = self.example_index {
|
||||
if let Some(index) = example_index.checked_sub(1) {
|
||||
self.example_index = Some(index);
|
||||
} else {
|
||||
self.example_index = Some(self.examples.len().saturating_sub(1));
|
||||
}
|
||||
} else if !self.examples.is_empty() {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
}
|
||||
MenuEvent::MoveDown => {
|
||||
if let Some(example_index) = self.example_index {
|
||||
let index = example_index + 1;
|
||||
if index < self.examples.len() {
|
||||
self.example_index = Some(index);
|
||||
} else {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
} else if !self.examples.is_empty() {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
}
|
||||
MenuEvent::MoveLeft => self.skipped_rows = self.skipped_rows.saturating_sub(1),
|
||||
MenuEvent::MoveRight => {
|
||||
let skipped = self.skipped_rows + 1;
|
||||
let description_rows = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.lines()
|
||||
.count();
|
||||
|
||||
let allowed_skips =
|
||||
description_rows.saturating_sub(self.working_details.description_rows);
|
||||
|
||||
if skipped < allowed_skips {
|
||||
self.skipped_rows = skipped;
|
||||
} else {
|
||||
self.skipped_rows = allowed_skips;
|
||||
}
|
||||
}
|
||||
MenuEvent::PreviousPage | MenuEvent::NextPage => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The buffer gets replaced in the Span location
|
||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||
let start = span.start.min(line_buffer.len());
|
||||
let end = span.end.min(line_buffer.len());
|
||||
|
||||
let string_len = if let Some(example_index) = self.example_index {
|
||||
let example = self
|
||||
.examples
|
||||
.get(example_index)
|
||||
.expect("the example index is always checked");
|
||||
|
||||
line_buffer.replace(start..end, example);
|
||||
example.len()
|
||||
} else {
|
||||
line_buffer.replace(start..end, &value);
|
||||
value.len()
|
||||
};
|
||||
|
||||
let mut offset = line_buffer.insertion_point();
|
||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||
line_buffer.set_insertion_point(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum rows that should be displayed by the menu
|
||||
fn min_rows(&self) -> u16 {
|
||||
self.get_rows().min(self.min_rows)
|
||||
}
|
||||
|
||||
/// Gets values from filler that will be displayed in the menu
|
||||
fn get_values(&self) -> &[Suggestion] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
|
||||
let example_lines = self
|
||||
.examples
|
||||
.iter()
|
||||
.fold(0, |acc, example| example.lines().count() + acc);
|
||||
|
||||
self.default_details.selection_rows
|
||||
+ self.default_details.description_rows as u16
|
||||
+ example_lines as u16
|
||||
+ 3
|
||||
}
|
||||
|
||||
fn menu_string(&self, _available_lines: u16, use_ansi_coloring: bool) -> String {
|
||||
if self.get_values().is_empty() {
|
||||
self.no_records_msg(use_ansi_coloring)
|
||||
} else {
|
||||
// The skip values represent the number of lines that should be skipped
|
||||
// while printing the menu
|
||||
let available_lines = self.default_details.selection_rows;
|
||||
let skip_values = if self.row_pos >= available_lines {
|
||||
let skip_lines = self.row_pos.saturating_sub(available_lines) + 1;
|
||||
(skip_lines * self.get_cols()) as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// It seems that crossterm prefers to have a complete string ready to be printed
|
||||
// rather than looping through the values and printing multiple things
|
||||
// This reduces the flickering when printing the menu
|
||||
let available_values = (available_lines * self.get_cols()) as usize;
|
||||
let selection_values: String = self
|
||||
.get_values()
|
||||
.iter()
|
||||
.skip(skip_values)
|
||||
.take(available_values)
|
||||
.enumerate()
|
||||
.map(|(index, suggestion)| {
|
||||
// Correcting the enumerate index based on the number of skipped values
|
||||
let index = index + skip_values;
|
||||
let column = index as u16 % self.get_cols();
|
||||
let empty_space = self.get_width().saturating_sub(suggestion.value.len());
|
||||
|
||||
self.create_entry_string(
|
||||
suggestion,
|
||||
index,
|
||||
column,
|
||||
empty_space,
|
||||
use_ansi_coloring,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
format!(
|
||||
"{}{}{}",
|
||||
selection_values,
|
||||
self.create_description_string(use_ansi_coloring),
|
||||
self.create_example_string(use_ansi_coloring)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
112
crates/nu-cli/src/menus/help_completions.rs
Normal file
112
crates/nu-cli/src/menus/help_completions.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||
use reedline::{Completer, Suggestion};
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||
|
||||
impl NuHelpCompleter {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self(engine_state)
|
||||
}
|
||||
|
||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let full_commands = self.0.get_signatures_with_examples(false);
|
||||
|
||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||
let mut commands = full_commands
|
||||
.iter()
|
||||
.filter(|(sig, _, _, _)| {
|
||||
sig.name.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
|
||||
.extra_usage
|
||||
.to_lowercase()
|
||||
.contains(&line.to_lowercase())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands.sort_by(|(a, _, _, _), (b, _, _, _)| {
|
||||
let a_distance = levenshtein_distance(line, &a.name);
|
||||
let b_distance = levenshtein_distance(line, &b.name);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
|
||||
commands
|
||||
.into_iter()
|
||||
.map(|(sig, examples, _, _)| {
|
||||
let mut long_desc = String::new();
|
||||
|
||||
let usage = &sig.usage;
|
||||
if !usage.is_empty() {
|
||||
long_desc.push_str(usage);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
let extra_usage = &sig.extra_usage;
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
|| !sig.optional_positional.is_empty()
|
||||
|| sig.rest_positional.is_some()
|
||||
{
|
||||
long_desc.push_str("\r\nParameters:\r\n");
|
||||
for positional in &sig.required_positional {
|
||||
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional) {}: {}\r\n",
|
||||
positional.name, positional.desc
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &sig.rest_positional {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" ...{}: {}\r\n",
|
||||
rest_positional.name, rest_positional.desc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let extra: Vec<String> = examples
|
||||
.iter()
|
||||
.map(|example| example.example.replace('\n', "\r\n"))
|
||||
.collect();
|
||||
|
||||
Suggestion {
|
||||
value: sig.name.clone(),
|
||||
description: Some(long_desc),
|
||||
extra: Some(extra),
|
||||
span: reedline::Span {
|
||||
start: pos,
|
||||
end: pos + line.len(),
|
||||
},
|
||||
append_whitespace: false,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuHelpCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
}
|
176
crates/nu-cli/src/menus/menu_completions.rs
Normal file
176
crates/nu-cli/src/menus/menu_completions.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use nu_engine::eval_block;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
IntoPipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
||||
use std::sync::Arc;
|
||||
|
||||
const SELECTION_CHAR: char = '!';
|
||||
|
||||
pub struct NuMenuCompleter {
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
only_buffer_difference: bool,
|
||||
}
|
||||
|
||||
impl NuMenuCompleter {
|
||||
pub fn new(
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
only_buffer_difference: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_id,
|
||||
span,
|
||||
stack,
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuMenuCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let parsed = parse_selection_char(line, SELECTION_CHAR);
|
||||
|
||||
let block = self.engine_state.get_block(self.block_id);
|
||||
|
||||
if let Some(buffer) = block.signature.get_positional(0) {
|
||||
if let Some(buffer_id) = &buffer.var_id {
|
||||
let line_buffer = Value::String {
|
||||
val: parsed.remainder.to_string(),
|
||||
span: self.span,
|
||||
};
|
||||
self.stack.add_var(*buffer_id, line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(position) = block.signature.get_positional(1) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
let line_buffer = Value::Int {
|
||||
val: pos as i64,
|
||||
span: self.span,
|
||||
};
|
||||
self.stack.add_var(*position_id, line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
let input = Value::nothing(self.span).into_pipeline_data();
|
||||
let res = eval_block(
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
block,
|
||||
input,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Ok(values) = res {
|
||||
let values = values.into_value(self.span);
|
||||
convert_to_suggestions(values, line, pos, self.only_buffer_difference)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_suggestions(
|
||||
value: Value,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
only_buffer_difference: bool,
|
||||
) -> Vec<Suggestion> {
|
||||
match value {
|
||||
Value::Record { .. } => {
|
||||
let text = match value
|
||||
.get_data_by_key("value")
|
||||
.and_then(|val| val.as_string().ok())
|
||||
{
|
||||
Some(val) => val,
|
||||
None => "No value key".to_string(),
|
||||
};
|
||||
|
||||
let description = value
|
||||
.get_data_by_key("description")
|
||||
.and_then(|val| val.as_string().ok());
|
||||
|
||||
let span = match value.get_data_by_key("span") {
|
||||
Some(span @ Value::Record { .. }) => {
|
||||
let start = span
|
||||
.get_data_by_key("start")
|
||||
.and_then(|val| val.as_integer().ok());
|
||||
let end = span
|
||||
.get_data_by_key("end")
|
||||
.and_then(|val| val.as_integer().ok());
|
||||
match (start, end) {
|
||||
(Some(start), Some(end)) => {
|
||||
let start = start.min(end);
|
||||
reedline::Span {
|
||||
start: start as usize,
|
||||
end: end as usize,
|
||||
}
|
||||
}
|
||||
_ => reedline::Span {
|
||||
start: if only_buffer_difference { pos } else { 0 },
|
||||
end: if only_buffer_difference {
|
||||
pos + line.len()
|
||||
} else {
|
||||
line.len()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => reedline::Span {
|
||||
start: if only_buffer_difference { pos } else { 0 },
|
||||
end: if only_buffer_difference {
|
||||
pos + line.len()
|
||||
} else {
|
||||
line.len()
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let extra = match value.get_data_by_key("extra") {
|
||||
Some(Value::List { vals, .. }) => {
|
||||
let extra: Vec<String> = vals
|
||||
.into_iter()
|
||||
.filter_map(|extra| match extra {
|
||||
Value::String { val, .. } => Some(val),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(extra)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
vec![Suggestion {
|
||||
value: text,
|
||||
description,
|
||||
extra,
|
||||
span,
|
||||
append_whitespace: false,
|
||||
}]
|
||||
}
|
||||
Value::List { vals, .. } => vals
|
||||
.into_iter()
|
||||
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||
.collect(),
|
||||
_ => vec![Suggestion {
|
||||
value: format!("Not a record: {:?}", value),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: 0,
|
||||
end: line.len(),
|
||||
},
|
||||
append_whitespace: false,
|
||||
}],
|
||||
}
|
||||
}
|
7
crates/nu-cli/src/menus/mod.rs
Normal file
7
crates/nu-cli/src/menus/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod description_menu;
|
||||
mod help_completions;
|
||||
mod menu_completions;
|
||||
|
||||
pub use description_menu::DescriptionMenu;
|
||||
pub use help_completions::NuHelpCompleter;
|
||||
pub use menu_completions::NuMenuCompleter;
|
67
crates/nu-cli/src/nu_highlight.rs
Normal file
67
crates/nu-cli/src/nu_highlight.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
||||
use reedline::Highlighter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuHighlight;
|
||||
|
||||
impl Command for NuHighlight {
|
||||
fn name(&self) -> &str {
|
||||
"nu-highlight"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight").category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Syntax highlight the input string."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["syntax", "color", "convert"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let config = engine_state.get_config().clone();
|
||||
|
||||
let highlighter = crate::NuHighlighter {
|
||||
engine_state,
|
||||
config,
|
||||
};
|
||||
|
||||
input.map(
|
||||
move |x| match x.as_string() {
|
||||
Ok(line) => {
|
||||
let highlights = highlighter.highlight(&line, line.len());
|
||||
|
||||
Value::String {
|
||||
val: highlights.render_simple(),
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(err) => Value::Error { error: err },
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Describe the type of a string",
|
||||
example: "'let x = 3' | nu-highlight",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
77
crates/nu-cli/src/print.rs
Normal file
77
crates/nu-cli/src/print.rs
Normal file
@ -0,0 +1,77 @@
|
||||
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, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Print;
|
||||
|
||||
impl Command for Print {
|
||||
fn name(&self) -> &str {
|
||||
"print"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("print")
|
||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||
.switch(
|
||||
"no-newline",
|
||||
"print without inserting a newline for the line ending",
|
||||
Some('n'),
|
||||
)
|
||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag("no-newline");
|
||||
let to_stderr = call.has_flag("stderr");
|
||||
let head = call.head;
|
||||
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Print 'hello world'",
|
||||
example: r#"print "hello world""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print the sum of 2 and 3",
|
||||
example: r#"print (2 + 3)"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
165
crates/nu-cli/src/prompt.rs
Normal file
165
crates/nu-cli/src/prompt.rs
Normal file
@ -0,0 +1,165 @@
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::DefaultPrompt;
|
||||
use {
|
||||
reedline::{
|
||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||
},
|
||||
std::borrow::Cow,
|
||||
};
|
||||
|
||||
/// Nushell prompt definition
|
||||
#[derive(Clone)]
|
||||
pub struct NushellPrompt {
|
||||
left_prompt_string: Option<String>,
|
||||
right_prompt_string: Option<String>,
|
||||
default_prompt_indicator: Option<String>,
|
||||
default_vi_insert_prompt_indicator: Option<String>,
|
||||
default_vi_normal_prompt_indicator: Option<String>,
|
||||
default_multiline_indicator: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for NushellPrompt {
|
||||
fn default() -> Self {
|
||||
NushellPrompt::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NushellPrompt {
|
||||
pub fn new() -> NushellPrompt {
|
||||
NushellPrompt {
|
||||
left_prompt_string: None,
|
||||
right_prompt_string: None,
|
||||
default_prompt_indicator: None,
|
||||
default_vi_insert_prompt_indicator: None,
|
||||
default_vi_normal_prompt_indicator: None,
|
||||
default_multiline_indicator: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_prompt_left(&mut self, prompt_string: Option<String>) {
|
||||
self.left_prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
||||
self.right_prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||
self.default_prompt_indicator = prompt_indicator_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: Option<String>) {
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: Option<String>) {
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: Option<String>) {
|
||||
self.default_multiline_indicator = prompt_multiline_indicator_string;
|
||||
}
|
||||
|
||||
pub fn update_all_prompt_strings(
|
||||
&mut self,
|
||||
left_prompt_string: Option<String>,
|
||||
right_prompt_string: Option<String>,
|
||||
prompt_indicator_string: Option<String>,
|
||||
prompt_multiline_indicator_string: Option<String>,
|
||||
prompt_vi: (Option<String>, Option<String>),
|
||||
) {
|
||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||
|
||||
self.left_prompt_string = left_prompt_string;
|
||||
self.right_prompt_string = right_prompt_string;
|
||||
self.default_prompt_indicator = prompt_indicator_string;
|
||||
self.default_multiline_indicator = prompt_multiline_indicator_string;
|
||||
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
}
|
||||
|
||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||
format!("({})", str)
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_right(&self) -> Cow<str> {
|
||||
if let Some(prompt_string) = &self.right_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
default
|
||||
.render_prompt_right()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||
match edit_mode {
|
||||
PromptEditMode::Default => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => ": ".into(),
|
||||
},
|
||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
},
|
||||
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
match &self.default_multiline_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "::: ".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
history_search: PromptHistorySearch,
|
||||
) -> Cow<str> {
|
||||
let prefix = match history_search.status {
|
||||
PromptHistorySearchStatus::Passing => "",
|
||||
PromptHistorySearchStatus::Failing => "failing ",
|
||||
};
|
||||
|
||||
Cow::Owned(format!(
|
||||
"({}reverse-search: {})",
|
||||
prefix, history_search.term
|
||||
))
|
||||
}
|
||||
}
|
174
crates/nu-cli/src/prompt_update.rs
Normal file
174
crates/nu-cli/src/prompt_update.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use crate::util::report_error;
|
||||
use crate::NushellPrompt;
|
||||
use log::info;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use reedline::Prompt;
|
||||
|
||||
// Name of environment variable where the prompt could be stored
|
||||
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||
pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT";
|
||||
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_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||
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(
|
||||
prompt: &str,
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
is_perf_true: bool,
|
||||
) -> Option<String> {
|
||||
stack
|
||||
.get_env_var(engine_state, prompt)
|
||||
.and_then(|v| match v {
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
captures,
|
||||
..
|
||||
} => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
let mut stack = stack.captures_to_stack(&captures);
|
||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||
let ret_val = eval_subexpression(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||
);
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
match ret_val {
|
||||
Ok(ret_val) => Some(ret_val),
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|pipeline_data| {
|
||||
let output = pipeline_data.collect_string("", config).ok();
|
||||
|
||||
match output {
|
||||
Some(mut x) => {
|
||||
// Just remove the very last newline.
|
||||
if x.ends_with('\n') {
|
||||
x.pop();
|
||||
}
|
||||
|
||||
if x.ends_with('\r') {
|
||||
x.pop();
|
||||
}
|
||||
Some(x)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_prompt<'prompt>(
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
nu_prompt: &'prompt mut NushellPrompt,
|
||||
is_perf_true: bool,
|
||||
) -> &'prompt dyn Prompt {
|
||||
let mut stack = stack.clone();
|
||||
|
||||
let left_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
match left_prompt_string {
|
||||
Some(prompt_string) => Some(format!(
|
||||
"{}{}{}",
|
||||
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||
)),
|
||||
None => left_prompt_string,
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
};
|
||||
|
||||
let right_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_indicator_string = get_prompt_string(
|
||||
PROMPT_INDICATOR,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_multiline_string = get_prompt_string(
|
||||
PROMPT_MULTILINE_INDICATOR,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_vi_insert_string = get_prompt_string(
|
||||
PROMPT_INDICATOR_VI_INSERT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_vi_normal_string = get_prompt_string(
|
||||
PROMPT_INDICATOR_VI_NORMAL,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
// apply the other indicators
|
||||
nu_prompt.update_all_prompt_strings(
|
||||
left_prompt_string,
|
||||
right_prompt_string,
|
||||
prompt_indicator_string,
|
||||
prompt_multiline_string,
|
||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||
);
|
||||
|
||||
let ret_val = nu_prompt as &dyn Prompt;
|
||||
if is_perf_true {
|
||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
ret_val
|
||||
}
|
1196
crates/nu-cli/src/reedline_config.rs
Normal file
1196
crates/nu-cli/src/reedline_config.rs
Normal file
File diff suppressed because it is too large
Load Diff
811
crates/nu-cli/src/repl.rs
Normal file
811
crates/nu-cli/src/repl.rs
Normal file
@ -0,0 +1,811 @@
|
||||
use crate::{
|
||||
completions::NuCompleter,
|
||||
prompt_update,
|
||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{info, trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{lex, parse};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use regex::Regex;
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
// These first two have been moved to prompt_update to get as close as possible to the prompt.
|
||||
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
|
||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
nushell_path: &str,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"translate environment vars {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// seed env vars
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: "0823".to_string(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".into(),
|
||||
Value::Int {
|
||||
val: 0,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config initially {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
// Get the config once for the history `max_history_size`
|
||||
// Updating that will not be possible in one session
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
let mut line_editor = Reedline::create();
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
);
|
||||
if let Some(history_path) = history_path.as_deref() {
|
||||
if is_perf_true {
|
||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::PlainText => Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
config.max_history_size as usize,
|
||||
history_path.to_path_buf(),
|
||||
)
|
||||
.into_diagnostic()?,
|
||||
),
|
||||
HistoryFileFormat::Sqlite => Box::new(
|
||||
SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?,
|
||||
),
|
||||
};
|
||||
line_editor = line_editor.with_history(history);
|
||||
};
|
||||
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
loop {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config each loop {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
// Reset the SIGQUIT handler
|
||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
||||
sig_quit.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let color_hm = get_color_config(config);
|
||||
|
||||
if is_perf_true {
|
||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
line_editor = line_editor
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_state.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_reference.clone(),
|
||||
stack.clone(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring);
|
||||
|
||||
line_editor = if config.use_ansi_coloring {
|
||||
line_editor.with_hinter(Box::new(
|
||||
DefaultHinter::default().with_style(color_hm["hints"]),
|
||||
))
|
||||
} else {
|
||||
line_editor.disable_hints()
|
||||
};
|
||||
|
||||
line_editor = match add_menus(line_editor, engine_reference, stack, config) {
|
||||
Ok(line_editor) => line_editor,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
Reedline::create()
|
||||
}
|
||||
};
|
||||
|
||||
let buffer_editor = if !config.buffer_editor.is_empty() {
|
||||
Some(config.buffer_editor.clone())
|
||||
} else {
|
||||
stack
|
||||
.get_env_var(engine_state, "EDITOR")
|
||||
.map(|v| v.as_string().unwrap_or_default())
|
||||
.filter(|v| !v.is_empty())
|
||||
.or_else(|| {
|
||||
stack
|
||||
.get_env_var(engine_state, "VISUAL")
|
||||
.map(|v| v.as_string().unwrap_or_default())
|
||||
.filter(|v| !v.is_empty())
|
||||
})
|
||||
};
|
||||
|
||||
line_editor = if let Some(buffer_editor) = buffer_editor {
|
||||
line_editor.with_buffer_editor(buffer_editor, "nu".into())
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
|
||||
if config.sync_history_on_enter {
|
||||
if is_perf_true {
|
||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = match create_keybindings(config) {
|
||||
Ok(keybindings) => match keybindings {
|
||||
KeybindingsMode::Emacs(keybindings) => {
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
KeybindingsMode::Vi {
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
} => {
|
||||
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
let prompt =
|
||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
||||
|
||||
entry_num += 1;
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"finished setup, starting repl {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
let input = line_editor.read_line(prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = sys.host_name();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
// if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
// let path = cwd.as_string()?;
|
||||
// // Try to abbreviate string for windows title
|
||||
// let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
// path.replace(&p.as_path().display().to_string(), "~")
|
||||
// } else {
|
||||
// path
|
||||
// };
|
||||
|
||||
// // Set window title too
|
||||
// // https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// // ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// // ESC]1;stringBEL -- Set icon name to string
|
||||
// // ESC]2;stringBEL -- Set window title to string
|
||||
// run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
// }
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
let path = nu_path::expand_path_with(&s, &cwd);
|
||||
|
||||
let orig = s.clone();
|
||||
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
let (path, span) = {
|
||||
if !path.exists() {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::DirectoryNotFound(tokens.0[0].span, None),
|
||||
);
|
||||
}
|
||||
let path = nu_path::canonicalize_with(path, &cwd)
|
||||
.expect("internal error: cannot canonicalize known path");
|
||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||
};
|
||||
|
||||
stack.add_env_var(
|
||||
"OLDPWD".into(),
|
||||
Value::String {
|
||||
val: cwd.clone(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.clone(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
let cwd = Value::String { val: cwd, span };
|
||||
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let mut shells = if let Some(v) = shells {
|
||||
v.as_list()
|
||||
.map(|x| x.to_vec())
|
||||
.unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
|
||||
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
||||
let current_shell = if let Some(v) = current_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::String { val: path, span };
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
|
||||
} else {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
}
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: format!("{}", cmd_duration.as_millis()),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
c.exit_status = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
let exit_code = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
|
||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
}
|
||||
|
||||
pub fn eval_env_change_hook(
|
||||
env_change_hook: Option<Value>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(hook) = env_change_hook {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols: env_names,
|
||||
vals: hook_values,
|
||||
..
|
||||
} => {
|
||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let after = stack
|
||||
.get_env_var(engine_state, env_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
if before != after {
|
||||
eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||
hook_value,
|
||||
)?;
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_name.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::TypeMismatch(
|
||||
"record for the 'env_change' hook".to_string(),
|
||||
x.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_hook(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<(String, Value)>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_span = value.span()?;
|
||||
|
||||
let condition_path = PathMember::String {
|
||||
val: "condition".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
let code_path = PathMember::String {
|
||||
val: "code".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
eval_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
}
|
||||
Value::Record { .. } => {
|
||||
let do_run_hook =
|
||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
||||
match condition {
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
match run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
arguments.clone(),
|
||||
block_span,
|
||||
) {
|
||||
Ok(value) => match value {
|
||||
Value::Bool { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"boolean output".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always run the hook
|
||||
true
|
||||
};
|
||||
|
||||
if do_run_hook {
|
||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
||||
Value::String {
|
||||
val,
|
||||
span: source_span,
|
||||
} => {
|
||||
let (block, delta, vars) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||
|
||||
for (name, val) in arguments {
|
||||
let var_id = working_set.add_variable(
|
||||
name.as_bytes().to_vec(),
|
||||
val.span()?,
|
||||
Type::Any,
|
||||
);
|
||||
|
||||
vars.push((var_id, val));
|
||||
}
|
||||
|
||||
let (output, err) =
|
||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"valid source code".into(),
|
||||
"source code with syntax errors".into(),
|
||||
source_span,
|
||||
));
|
||||
}
|
||||
|
||||
(output, working_set.render(), vars)
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
let input = PipelineData::new(value_span);
|
||||
|
||||
let var_ids: Vec<VarId> = vars
|
||||
.into_iter()
|
||||
.map(|(var_id, val)| {
|
||||
stack.add_var(var_id, val);
|
||||
var_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
for var_id in var_ids.iter() {
|
||||
stack.vars.remove(var_id);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block or string".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block, record, or list of records".into(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<(String, Value)>,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
if let Some(arg) = arguments.get(idx) {
|
||||
callee_stack.add_var(*var_id, arg.1.clone())
|
||||
} else {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"This hook block has too many parameters".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
val => {
|
||||
// If all went fine, preserve the environment of the called block
|
||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||
|
||||
// remove env vars that are present in the caller but not in the callee
|
||||
// (the callee hid them)
|
||||
for var in caller_env_vars.iter() {
|
||||
if !callee_stack.has_env_var(engine_state, var) {
|
||||
stack.remove_env_var(engine_state, var);
|
||||
}
|
||||
}
|
||||
|
||||
// add new env vars from callee to caller
|
||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||
stack.add_env_var(var, value);
|
||||
}
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
match io::stdout().write_all(seq.as_bytes()) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error writing ansi sequence".into(),
|
||||
err.to_string(),
|
||||
Some(Span { start: 0, end: 0 }),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
io::stdout().flush().map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error flushing stdio".into(),
|
||||
e.to_string(),
|
||||
Some(Span { start: 0, end: 0 }),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||
static ref DRIVE_PATH_REGEX: Regex =
|
||||
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
|
||||
}
|
||||
|
||||
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||
fn looks_like_path(orig: &str) -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if DRIVE_PATH_REGEX.is_match(orig) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\')
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_path_windows_drive_path_works() {
|
||||
let on_windows = cfg!(windows);
|
||||
assert_eq!(looks_like_path("C:"), on_windows);
|
||||
assert_eq!(looks_like_path("D:\\"), on_windows);
|
||||
assert_eq!(looks_like_path("E:/"), on_windows);
|
||||
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
|
||||
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
|
||||
}
|
218
crates/nu-cli/src/syntax_highlight.rs
Normal file
218
crates/nu-cli/src/syntax_highlight.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use log::trace;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::get_shape_color;
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use nu_protocol::Config;
|
||||
use reedline::{Highlighter, StyledText};
|
||||
|
||||
pub struct NuHighlighter {
|
||||
pub engine_state: EngineState,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
impl Highlighter for NuHighlighter {
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
|
||||
let shapes = flatten_block(&working_set, &block);
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
};
|
||||
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
output.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => output.push((
|
||||
// nushell Garbage
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Nothing => output.push((
|
||||
// nushell Nothing
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Binary => {
|
||||
// nushell ?
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
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 => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Table => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Record => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Block => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Filepath => output.push((
|
||||
// nushell Path
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Directory => output.push((
|
||||
// nushell Directory
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::GlobPattern => output.push((
|
||||
// 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;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
output.push((Style::new(), remainder));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
362
crates/nu-cli/src/util.rs
Normal file
362
crates/nu-cli/src/util.rs
Normal file
@ -0,0 +1,362 @@
|
||||
use log::trace;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::CliError;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// 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,
|
||||
init_cwd: &Path,
|
||||
) {
|
||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
format!("Environment variable was not captured: {}", env_str),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(msg.into()),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn put_env_to_fake_file(name: &str, val: &str, fake_env_file: &mut String) {
|
||||
fake_env_file.push_str(&escape_quote_string(name));
|
||||
fake_env_file.push('=');
|
||||
fake_env_file.push_str(&escape_quote_string(val));
|
||||
fake_env_file.push('\n');
|
||||
}
|
||||
|
||||
let mut fake_env_file = String::new();
|
||||
// Write all the env vars into a fake file
|
||||
for (name, val) in vars {
|
||||
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
||||
}
|
||||
|
||||
match init_cwd.to_str() {
|
||||
Some(cwd) => {
|
||||
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
|
||||
}
|
||||
None => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
"Current directory is not a valid utf-8 path".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!(
|
||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||
init_cwd
|
||||
)),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Lex the fake file, assign spans to all environment variables and add them
|
||||
// to stack
|
||||
let span_offset = engine_state.next_span_start();
|
||||
|
||||
engine_state.add_file(
|
||||
"Host Environment Variables".to_string(),
|
||||
fake_env_file.as_bytes().to_vec(),
|
||||
);
|
||||
|
||||
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
||||
|
||||
for token in tokens {
|
||||
if let Token {
|
||||
contents: TokenContents::Item,
|
||||
span: full_span,
|
||||
} = token
|
||||
{
|
||||
let contents = engine_state.get_span_contents(&full_span);
|
||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||
|
||||
let name = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(0)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
|
||||
if parse_error.is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got unparsable name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bytes
|
||||
} else {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
let value = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(2)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
|
||||
if parse_error.is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got unparsable value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Value::String {
|
||||
val: bytes,
|
||||
span: *span,
|
||||
}
|
||||
} else {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
// stack.add_env_var(name, value);
|
||||
engine_state.add_env_var(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_source(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
source: &[u8],
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
) -> bool {
|
||||
trace!("eval_source");
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, err) = parse(
|
||||
&mut working_set,
|
||||
Some(fname), // format!("entry #{}", entry_num)
|
||||
source,
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
if let Some(err) = err {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error(&working_set, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error_new(engine_state, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
set_last_exit_code(stack, 1);
|
||||
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::Int {
|
||||
val: exit_code,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn report_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = nu_utils::enable_vt_processing();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
Err(_) => match std::env::var("PWD") {
|
||||
Ok(cwd) => PathBuf::from(cwd),
|
||||
Err(_) => match nu_path::home_dir() {
|
||||
Some(cwd) => cwd,
|
||||
None => PathBuf::new(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gather_env_vars() {
|
||||
let mut engine_state = EngineState::new();
|
||||
let symbols = r##" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
|
||||
|
||||
gather_env_vars(
|
||||
[
|
||||
("FOO".into(), "foo".into()),
|
||||
("SYMBOLS".into(), symbols.into()),
|
||||
(symbols.into(), "symbols".into()),
|
||||
]
|
||||
.into_iter(),
|
||||
&mut engine_state,
|
||||
Path::new("t"),
|
||||
);
|
||||
|
||||
let env = engine_state.render_env_vars();
|
||||
|
||||
assert!(
|
||||
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
|
||||
);
|
||||
assert!(env.get(&"PWD".to_string()).is_some());
|
||||
assert_eq!(env.len(), 4);
|
||||
}
|
||||
}
|
20
crates/nu-cli/src/validation.rs
Normal file
20
crates/nu-cli/src/validation.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use nu_parser::{parse, ParseError};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use reedline::{ValidationResult, Validator};
|
||||
|
||||
pub struct NuValidator {
|
||||
pub engine_state: EngineState,
|
||||
}
|
||||
|
||||
impl Validator for NuValidator {
|
||||
fn validate(&self, line: &str) -> ValidationResult {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let (_, err) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
|
||||
if matches!(err, Some(ParseError::UnexpectedEof(..))) {
|
||||
ValidationResult::Incomplete
|
||||
} else {
|
||||
ValidationResult::Complete
|
||||
}
|
||||
}
|
||||
}
|
65
crates/nu-cli/tests/alias.rs
Normal file
65
crates/nu-cli/tests/alias.rs
Normal file
@ -0,0 +1,65 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[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)
|
||||
}
|
69
crates/nu-cli/tests/custom_completions.rs
Normal file
69
crates/nu-cli/tests/custom_completions.rs
Normal file
@ -0,0 +1,69 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{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)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_double_dash_argument(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_completions_single_dash_argument(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_completions_command(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 9);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_subcommands(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_completions_subcommands_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);
|
||||
}
|
28
crates/nu-cli/tests/dotnu_completions.rs
Normal file
28
crates/nu-cli/tests/dotnu_completions.rs
Normal file
@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
272
crates/nu-cli/tests/file_completions.rs
Normal file
272
crates/nu-cli/tests/file_completions.rs
Normal file
@ -0,0 +1,272 @@
|
||||
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 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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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)
|
||||
}
|
38
crates/nu-cli/tests/flag_completions.rs
Normal file
38
crates/nu-cli/tests/flag_completions.rs
Normal file
@ -0,0 +1,38 @@
|
||||
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 -", 4);
|
||||
|
||||
assert_eq!(14, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--directory".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--short-names".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
"-f".into(),
|
||||
"-h".into(),
|
||||
"-l".into(),
|
||||
"-s".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
29
crates/nu-cli/tests/folder_completions.rs
Normal file
29
crates/nu-cli/tests/folder_completions.rs
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
128
crates/nu-cli/tests/support/completions_helpers.rs
Normal file
128
crates/nu-cli/tests/support/completions_helpers.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_command::create_default_context;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
use reedline::Suggestion;
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
// creates a new engine with the current path into the completions fixtures folder
|
||||
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("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 {
|
||||
start: 0,
|
||||
end: dir_str.len(),
|
||||
},
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"TEST".to_string(),
|
||||
Value::String {
|
||||
val: "NUSHELL".to_string(),
|
||||
span: nu_protocol::Span {
|
||||
start: 0,
|
||||
end: dir_str.len(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
}
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
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| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
}
|
||||
|
||||
// append the separator to the converted path
|
||||
pub fn folder(path: PathBuf) -> String {
|
||||
let mut converted_path = file(path);
|
||||
converted_path.push(SEP);
|
||||
|
||||
converted_path
|
||||
}
|
||||
|
||||
// convert a given path to string
|
||||
pub fn file(path: PathBuf) -> String {
|
||||
path.into_os_string().into_string().unwrap_or_default()
|
||||
}
|
||||
|
||||
// merge_input executes the given input into the engine
|
||||
// and merges the state
|
||||
pub fn merge_input(
|
||||
input: &[u8],
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
dir: PathBuf,
|
||||
) -> Result<(), ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
||||
|
||||
assert!(err.is_none());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::Value(
|
||||
Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
None
|
||||
),
|
||||
false,
|
||||
false
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Merge environment into the permanent state
|
||||
engine_state.merge_env(stack, &dir)
|
||||
}
|
3
crates/nu-cli/tests/support/mod.rs
Normal file
3
crates/nu-cli/tests/support/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod completions_helpers;
|
||||
|
||||
pub use completions_helpers::{file, folder, match_suggestions, merge_input, new_engine};
|
88
crates/nu-cli/tests/variables_completions.rs
Normal file
88
crates/nu-cli/tests/variables_completions.rs
Normal file
@ -0,0 +1,88 @@
|
||||
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.", 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);
|
||||
}
|
14
crates/nu-color-config/Cargo.toml
Normal file
14
crates/nu-color-config/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Color configuration code used by Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.66.2"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.2" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.66.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.2" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
21
crates/nu-color-config/LICENSE
Normal file
21
crates/nu-color-config/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
418
crates/nu-color-config/src/color_config.rs
Normal file
418
crates/nu-color-config/src/color_config.rs
Normal file
@ -0,0 +1,418 @@
|
||||
use crate::nu_style::{color_from_hex, color_string_to_nustyle};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_protocol::Config;
|
||||
use nu_table::{Alignment, TextStyle};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn lookup_ansi_color_style(s: &str) -> Style {
|
||||
if s.starts_with('#') {
|
||||
match color_from_hex(s) {
|
||||
Ok(c) => match c {
|
||||
Some(c) => c.normal(),
|
||||
None => Style::default(),
|
||||
},
|
||||
Err(_) => Style::default(),
|
||||
}
|
||||
} else if s.starts_with('{') {
|
||||
color_string_to_nustyle(s.to_string())
|
||||
} else {
|
||||
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(),
|
||||
|
||||
_ => Color::White.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hashmap(key: &str, val: &str, hm: &mut 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();
|
||||
// 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 {
|
||||
let value = 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
|
||||
}
|
||||
|
||||
// This function will assign a text style to a primitive, or really any string that's
|
||||
// in the hashmap. The hashmap actually contains the style to be applied.
|
||||
pub fn style_primitive(primitive: &str, color_hm: &HashMap<String, Style>) -> TextStyle {
|
||||
match primitive {
|
||||
"bool" => {
|
||||
let style = color_hm.get(primitive);
|
||||
match style {
|
||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
||||
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 test_hm() {
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
let mut hm: HashMap<String, Style> = HashMap::new();
|
||||
hm.insert("primitive_int".to_string(), Color::White.normal());
|
||||
hm.insert("primitive_decimal".to_string(), Color::White.normal());
|
||||
hm.insert("primitive_filesize".to_string(), Color::White.normal());
|
||||
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());
|
||||
hm.insert("primitive_boolean".to_string(), Color::White.normal());
|
||||
hm.insert("primitive_date".to_string(), Color::White.normal());
|
||||
hm.insert("primitive_duration".to_string(), Color::White.normal());
|
||||
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());
|
||||
hm.insert("separator".to_string(), Color::White.normal());
|
||||
hm.insert("header_align".to_string(), Color::Green.bold());
|
||||
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());
|
||||
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());
|
||||
}
|
7
crates/nu-color-config/src/lib.rs
Normal file
7
crates/nu-color-config/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod color_config;
|
||||
mod nu_style;
|
||||
mod shape_color;
|
||||
|
||||
pub use color_config::*;
|
||||
pub use nu_style::*;
|
||||
pub use shape_color::*;
|
103
crates/nu-color-config/src/nu_style.rs
Normal file
103
crates/nu-color-config/src/nu_style.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct NuStyle {
|
||||
pub fg: Option<String>,
|
||||
pub bg: Option<String>,
|
||||
pub attr: Option<String>,
|
||||
}
|
||||
|
||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||
// get the nu_ansi_term::Color foreground color
|
||||
let fg_color = match nu_style.fg {
|
||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
||||
_ => None,
|
||||
};
|
||||
// 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 {
|
||||
// eprintln!("color_string: {}", &color_string);
|
||||
if color_string.chars().count() < 1 {
|
||||
Style::default()
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_from_hex(
|
||||
hex_color: &str,
|
||||
) -> std::result::Result<Option<Color>, std::num::ParseIntError> {
|
||||
// right now we only allow hex colors with hashtag and 6 characters
|
||||
let trimmed = hex_color.trim_matches('#');
|
||||
if trimmed.len() != 6 {
|
||||
Ok(None)
|
||||
} else {
|
||||
// make a nu_ansi_term::Color::Rgb color by converting hex to decimal
|
||||
Ok(Some(Color::Rgb(
|
||||
u8::from_str_radix(&trimmed[..2], 16)?,
|
||||
u8::from_str_radix(&trimmed[2..4], 16)?,
|
||||
u8::from_str_radix(&trimmed[4..6], 16)?,
|
||||
)))
|
||||
}
|
||||
}
|
41
crates/nu-color-config/src/shape_color.rs
Normal file
41
crates/nu-color-config/src/shape_color.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::color_config::lookup_ansi_color_style;
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_protocol::Config;
|
||||
|
||||
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
||||
match conf.color_config.get(shape.as_str()) {
|
||||
Some(int_color) => match int_color.as_string() {
|
||||
Ok(int_color) => lookup_ansi_color_style(&int_color),
|
||||
Err(_) => Style::default(),
|
||||
},
|
||||
None => match shape.as_ref() {
|
||||
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
||||
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_bool" => Style::new().fg(Color::LightCyan),
|
||||
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
||||
"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(),
|
||||
},
|
||||
}
|
||||
}
|
136
crates/nu-command/Cargo.toml
Normal file
136
crates/nu-command/Cargo.toml
Normal file
@ -0,0 +1,136 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Nushell's built-in commands"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.66.3"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.2" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.66.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.66.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.2" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.66.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.2" }
|
||||
nu-system = { path = "../nu-system", version = "0.66.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.2" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.66.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.66.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.2" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
|
||||
# Potential dependencies for extras
|
||||
alphanumeric-sort = "1.4.4"
|
||||
base64 = "0.13.0"
|
||||
byteorder = "1.4.3"
|
||||
bytesize = "1.1.0"
|
||||
calamine = "0.18.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono-humanize = "0.2.1"
|
||||
chrono-tz = "0.6.1"
|
||||
crossterm = "0.23.0"
|
||||
csv = "1.1.6"
|
||||
dialoguer = "0.9.0"
|
||||
digest = "0.10.0"
|
||||
dtparse = "1.2.0"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.30"
|
||||
filesize = "0.2.0"
|
||||
filetime = "0.2.15"
|
||||
fs_extra = "1.2.0"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
indexmap = { version="1.7", features=["serde-1"] }
|
||||
Inflector = "0.11"
|
||||
is-root = "0.1.2"
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
lscolors = { version = "0.10.0", features = ["crossterm"]}
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
meval = "0.2.0"
|
||||
mime = "0.3.16"
|
||||
notify = "4.0.17"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
pathdiff = "0.2.1"
|
||||
powierza-coefficient = "1.0.1"
|
||||
quick-xml = "0.23.0"
|
||||
rand = "0.8"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "6.3.0"
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_ini = "0.2.0"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.10.0"
|
||||
# Disable default features b/c the default features build Git (very slow to compile)
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.24.6"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
toml = "0.5.8"
|
||||
unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
which = { version = "4.2.2", optional = true }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "2.0.0"
|
||||
users = "0.11.0"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||
version = "2.1.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.polars]
|
||||
version = "0.22.8"
|
||||
# path = "../../../../polars/polars"
|
||||
optional = true
|
||||
features = [
|
||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
||||
"rolling_window", "strings", "rows", "random",
|
||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
||||
"dynamic_groupby", "dtype-categorical", "concat_str"
|
||||
]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.37.0"
|
||||
features = [
|
||||
"alloc",
|
||||
"Win32_Foundation",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_SystemServices",
|
||||
]
|
||||
|
||||
[features]
|
||||
trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
plugin = ["nu-parser/plugin"]
|
||||
dataframe = ["polars", "num"]
|
||||
database = ["sqlparser", "rusqlite"]
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
rstest = "0.15.0"
|
21
crates/nu-command/LICENSE
Normal file
21
crates/nu-command/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1
crates/nu-command/assets/228_themes.json
Normal file
1
crates/nu-command/assets/228_themes.json
Normal file
File diff suppressed because one or more lines are too long
18
crates/nu-command/build.rs
Normal file
18
crates/nu-command/build.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
// 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)
|
||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
||||
Ok(String::from_utf8(out.stdout)
|
||||
.expect("could not convert stdout to string")
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
167
crates/nu-command/src/bytes/add.rs
Normal file
167
crates/nu-command/src/bytes/add.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value};
|
||||
|
||||
struct Arguments {
|
||||
added_data: Vec<u8>,
|
||||
index: Option<usize>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.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,
|
||||
"optionally matches prefix of text by column 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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_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,
|
||||
column_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(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 {})
|
||||
}
|
||||
}
|
281
crates/nu-command/src/bytes/at.rs
Normal file
281
crates/nu-command/src/bytes/at.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesAt;
|
||||
|
||||
struct Arguments {
|
||||
start: isize,
|
||||
end: isize,
|
||||
arg_span: Span,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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 given".to_string(),
|
||||
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,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
let start = vals.pop().expect("Already check has size 1");
|
||||
let start = match start {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
(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(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
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(),
|
||||
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(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok((start, end, span))
|
||||
}
|
||||
|
||||
impl Command for BytesAt {
|
||||
fn name(&self) -> &str {
|
||||
"bytes at"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes at")
|
||||
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally get bytes by column 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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
start,
|
||||
end,
|
||||
arg_span,
|
||||
column_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(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::UnsupportedInput(
|
||||
"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 {})
|
||||
}
|
||||
}
|
81
crates/nu-command/src/bytes/build_.rs
Normal file
81
crates/nu-command/src/bytes/build_.rs
Normal file
@ -0,0 +1,81 @@
|
||||
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,
|
||||
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")
|
||||
.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),
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"only support expression which yields to binary data".to_string(),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, 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)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Various commands for working with byte data"
|
||||
}
|
||||
|
||||
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),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Bytes;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Bytes {})
|
||||
}
|
||||
}
|
127
crates/nu-command/src/bytes/collect.rs
Normal file
127
crates/nu-command/src/bytes/collect.rs
Normal file
@ -0,0 +1,127 @@
|
||||
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,
|
||||
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")
|
||||
.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)
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"The element type is {}, this command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {})
|
||||
}
|
||||
}
|
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_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::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
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::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[11]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.ends_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesEndsWith {})
|
||||
}
|
||||
}
|
210
crates/nu-command/src/bytes/index_of.rs
Normal file
210
crates/nu-command/src/bytes/index_of.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
all: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::Binary,
|
||||
"the pattern to find index of",
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally returns index of pattern in string by column 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", "index"]
|
||||
}
|
||||
|
||||
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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
end: call.has_flag("end"),
|
||||
all: call.has_flag("all"),
|
||||
column_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(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 super::{operate, BytesArgument};
|
||||
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, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesLen;
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for BytesLen {
|
||||
fn name(&self) -> &str {
|
||||
"bytes length"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes length")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find length of binary by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output the length of any bytes in the pipeline"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["len", "size", "count"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_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(input: &[u8], _arg: &Arguments, span: Span) -> Value {
|
||||
Value::Int {
|
||||
val: input.len() as i64,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesLen {})
|
||||
}
|
||||
}
|
98
crates/nu-command/src/bytes/mod.rs
Normal file
98
crates/nu-command/src/bytes/mod.rs
Normal file
@ -0,0 +1,98 @@
|
||||
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;
|
||||
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
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;
|
||||
|
||||
trait BytesArgument {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
|
||||
}
|
||||
|
||||
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
|
||||
fn operate<C, A>(
|
||||
cmd: C,
|
||||
mut arg: A,
|
||||
input: PipelineData,
|
||||
span: Span,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
) -> Result<PipelineData, ShellError>
|
||||
where
|
||||
A: BytesArgument + Send + Sync + 'static,
|
||||
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
|
||||
{
|
||||
match arg.take_column_paths() {
|
||||
None => input.map(
|
||||
move |v| match v {
|
||||
Value::Binary {
|
||||
val,
|
||||
span: val_span,
|
||||
} => cmd(&val, &arg, val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
},
|
||||
},
|
||||
ctrlc,
|
||||
),
|
||||
Some(column_paths) => {
|
||||
let arg = Arc::new(arg);
|
||||
input.map(
|
||||
move |mut v| {
|
||||
for path in &column_paths {
|
||||
let opt = arg.clone();
|
||||
let r = v.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| {
|
||||
match old {
|
||||
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
}}}),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
}
|
||||
}
|
||||
v
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
196
crates/nu-command/src/bytes/remove.rs
Normal file
196
crates/nu-command/src/bytes/remove.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally remove bytes by column 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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if pattern_to_remove.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"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"),
|
||||
column_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(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 remaing 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 {})
|
||||
}
|
||||
}
|
171
crates/nu-command/src/bytes/replace.rs
Normal file
171
crates/nu-command/src/bytes/replace.rs
Normal file
@ -0,0 +1,171 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
find: Vec<u8>,
|
||||
replace: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find and replace text by column 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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if find.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"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)?,
|
||||
column_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(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 {})
|
||||
}
|
||||
}
|
104
crates/nu-command/src/bytes/reverse.rs
Normal file
104
crates/nu-command/src/bytes/reverse.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value};
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesReverse;
|
||||
|
||||
impl Command for BytesReverse {
|
||||
fn name(&self) -> &str {
|
||||
"bytes reverse"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes reverse")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Reverse every 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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_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(input: &[u8], _args: &Arguments, span: Span) -> Value {
|
||||
let mut reversed_input = input.to_vec();
|
||||
reversed_input.reverse();
|
||||
Value::Binary {
|
||||
val: reversed_input,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesReverse {})
|
||||
}
|
||||
}
|
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use super::{operate, BytesArgument};
|
||||
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, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_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")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.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 column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_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::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.starts_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesStartsWith {})
|
||||
}
|
||||
}
|
317
crates/nu-command/src/charting/hashable_value.rs
Normal file
317
crates/nu-command/src/charting/hashable_value.rs
Normal file
@ -0,0 +1,317 @@
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use nu_protocol::{ShellError, Span, Value};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A subset of [Value](crate::Value), which is hashable.
|
||||
/// And it means that we can put the value into something like [HashMap](std::collections::HashMap) or [HashSet](std::collections::HashSet)
|
||||
/// for further usage like value statistics.
|
||||
///
|
||||
/// For now the main way to crate a [HashableValue] is using [from_value](HashableValue::from_value)
|
||||
///
|
||||
/// Please note that although each variant contains `span` field, but during hashing, this field will not be concerned.
|
||||
/// Which means that the following will be true:
|
||||
/// ```text
|
||||
/// 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)]
|
||||
pub enum HashableValue {
|
||||
Bool {
|
||||
val: bool,
|
||||
span: Span,
|
||||
},
|
||||
Int {
|
||||
val: i64,
|
||||
span: Span,
|
||||
},
|
||||
Float {
|
||||
val: [u8; 8], // because f64 is not hashable, we save it as [u8;8] array to make it hashable.
|
||||
span: Span,
|
||||
},
|
||||
Filesize {
|
||||
val: i64,
|
||||
span: Span,
|
||||
},
|
||||
Duration {
|
||||
val: i64,
|
||||
span: Span,
|
||||
},
|
||||
Date {
|
||||
val: DateTime<FixedOffset>,
|
||||
span: Span,
|
||||
},
|
||||
String {
|
||||
val: String,
|
||||
span: Span,
|
||||
},
|
||||
Binary {
|
||||
val: Vec<u8>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for HashableValue {
|
||||
fn default() -> Self {
|
||||
HashableValue::Bool {
|
||||
val: false,
|
||||
span: Span { start: 0, end: 0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashableValue {
|
||||
/// Try to convert from `value` to self
|
||||
///
|
||||
/// A `span` is required because when there is an error in value, it may not contain `span` field.
|
||||
///
|
||||
/// If the given value is not hashable(mainly because of it is structured data), an error will returned.
|
||||
pub fn from_value(value: Value, span: Span) -> Result<Self, ShellError> {
|
||||
match value {
|
||||
Value::Bool { val, span } => Ok(HashableValue::Bool { val, span }),
|
||||
Value::Int { val, span } => Ok(HashableValue::Int { val, span }),
|
||||
Value::Filesize { val, span } => Ok(HashableValue::Filesize { val, span }),
|
||||
Value::Duration { val, span } => Ok(HashableValue::Duration { val, span }),
|
||||
Value::Date { val, span } => Ok(HashableValue::Date { val, span }),
|
||||
Value::Float { val, span } => Ok(HashableValue::Float {
|
||||
val: val.to_ne_bytes(),
|
||||
span,
|
||||
}),
|
||||
Value::String { val, span } => Ok(HashableValue::String { val, span }),
|
||||
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
|
||||
|
||||
_ => {
|
||||
let input_span = value.span().unwrap_or(span);
|
||||
Err(ShellError::UnsupportedInput(
|
||||
format!("input value {value:?} is not hashable"),
|
||||
input_span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from self to nu's core data type `Value`.
|
||||
pub fn into_value(self) -> Value {
|
||||
match self {
|
||||
HashableValue::Bool { val, span } => Value::Bool { val, span },
|
||||
HashableValue::Int { val, span } => Value::Int { val, span },
|
||||
HashableValue::Filesize { val, span } => Value::Filesize { val, span },
|
||||
HashableValue::Duration { val, span } => Value::Duration { val, span },
|
||||
HashableValue::Date { val, span } => Value::Date { val, span },
|
||||
HashableValue::Float { val, span } => Value::Float {
|
||||
val: f64::from_ne_bytes(val),
|
||||
span,
|
||||
},
|
||||
HashableValue::String { val, span } => Value::String { val, span },
|
||||
HashableValue::Binary { val, span } => Value::Binary { val, span },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for HashableValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
HashableValue::Bool { val, .. } => val.hash(state),
|
||||
HashableValue::Int { val, .. } => val.hash(state),
|
||||
HashableValue::Filesize { val, .. } => val.hash(state),
|
||||
HashableValue::Duration { val, .. } => val.hash(state),
|
||||
HashableValue::Date { val, .. } => val.hash(state),
|
||||
HashableValue::Float { val, .. } => val.hash(state),
|
||||
HashableValue::String { val, .. } => val.hash(state),
|
||||
HashableValue::Binary { val, .. } => val.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HashableValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(HashableValue::Bool { val: lhs, .. }, HashableValue::Bool { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
(HashableValue::Int { val: lhs, .. }, HashableValue::Int { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
(
|
||||
HashableValue::Filesize { val: lhs, .. },
|
||||
HashableValue::Filesize { val: rhs, .. },
|
||||
) => lhs == rhs,
|
||||
(
|
||||
HashableValue::Duration { val: lhs, .. },
|
||||
HashableValue::Duration { val: rhs, .. },
|
||||
) => lhs == rhs,
|
||||
(HashableValue::Date { val: lhs, .. }, HashableValue::Date { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
(HashableValue::Float { val: lhs, .. }, HashableValue::Float { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
(HashableValue::String { val: lhs, .. }, HashableValue::String { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
(HashableValue::Binary { val: lhs, .. }, HashableValue::Binary { val: rhs, .. }) => {
|
||||
lhs == rhs
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_protocol::ast::{CellPath, PathMember};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[test]
|
||||
fn from_value() {
|
||||
let span = Span::test_data();
|
||||
let values = vec![
|
||||
(
|
||||
Value::Bool { val: true, span },
|
||||
HashableValue::Bool { val: true, span },
|
||||
),
|
||||
(
|
||||
Value::Int { val: 1, span },
|
||||
HashableValue::Int { val: 1, span },
|
||||
),
|
||||
(
|
||||
Value::Filesize { val: 1, span },
|
||||
HashableValue::Filesize { val: 1, span },
|
||||
),
|
||||
(
|
||||
Value::Duration { val: 1, span },
|
||||
HashableValue::Duration { val: 1, span },
|
||||
),
|
||||
(
|
||||
Value::Date {
|
||||
val: DateTime::<FixedOffset>::parse_from_rfc2822(
|
||||
"Wed, 18 Feb 2015 23:16:09 GMT",
|
||||
)
|
||||
.unwrap(),
|
||||
span,
|
||||
},
|
||||
HashableValue::Date {
|
||||
val: DateTime::<FixedOffset>::parse_from_rfc2822(
|
||||
"Wed, 18 Feb 2015 23:16:09 GMT",
|
||||
)
|
||||
.unwrap(),
|
||||
span,
|
||||
},
|
||||
),
|
||||
(
|
||||
Value::String {
|
||||
val: "1".to_string(),
|
||||
span,
|
||||
},
|
||||
HashableValue::String {
|
||||
val: "1".to_string(),
|
||||
span,
|
||||
},
|
||||
),
|
||||
(
|
||||
Value::Binary { val: vec![1], span },
|
||||
HashableValue::Binary { val: vec![1], span },
|
||||
),
|
||||
];
|
||||
for (val, expect_hashable_val) in values.into_iter() {
|
||||
assert_eq!(
|
||||
HashableValue::from_value(val, Span { start: 0, end: 0 }).unwrap(),
|
||||
expect_hashable_val
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_unhashable_value() {
|
||||
let span = Span::test_data();
|
||||
let values = [
|
||||
Value::List {
|
||||
vals: vec![Value::Bool { val: true, span }],
|
||||
span,
|
||||
},
|
||||
Value::Block {
|
||||
val: 0,
|
||||
captures: HashMap::new(),
|
||||
span,
|
||||
},
|
||||
Value::Nothing { span },
|
||||
Value::Error {
|
||||
error: ShellError::DidYouMean("what?".to_string(), span),
|
||||
},
|
||||
Value::CellPath {
|
||||
val: CellPath {
|
||||
members: vec![PathMember::Int { val: 0, span }],
|
||||
},
|
||||
span,
|
||||
},
|
||||
];
|
||||
for v in values {
|
||||
assert!(HashableValue::from_value(v, Span { start: 0, end: 0 }).is_err())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_to_tobe_same() {
|
||||
let span = Span::test_data();
|
||||
let values = vec![
|
||||
Value::Bool { val: true, span },
|
||||
Value::Int { val: 1, span },
|
||||
Value::Filesize { val: 1, span },
|
||||
Value::Duration { val: 1, span },
|
||||
Value::String {
|
||||
val: "1".to_string(),
|
||||
span,
|
||||
},
|
||||
Value::Binary { val: vec![1], span },
|
||||
];
|
||||
for val in values.into_iter() {
|
||||
let expected_val = val.clone();
|
||||
assert_eq!(
|
||||
HashableValue::from_value(val, Span { start: 0, end: 0 })
|
||||
.unwrap()
|
||||
.into_value(),
|
||||
expected_val
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashable_value_eq_without_concern_span() {
|
||||
assert_eq!(
|
||||
HashableValue::Bool {
|
||||
val: true,
|
||||
span: Span { start: 0, end: 1 }
|
||||
},
|
||||
HashableValue::Bool {
|
||||
val: true,
|
||||
span: Span {
|
||||
start: 90,
|
||||
end: 1000
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_to_hashset() {
|
||||
let span = Span::test_data();
|
||||
let mut set = HashSet::new();
|
||||
set.insert(HashableValue::Bool { val: true, span });
|
||||
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
||||
|
||||
// hashable value doesn't care about span.
|
||||
let diff_span = Span { start: 1, end: 2 };
|
||||
set.insert(HashableValue::Bool {
|
||||
val: true,
|
||||
span: diff_span,
|
||||
});
|
||||
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
||||
assert!(set.contains(&HashableValue::Bool {
|
||||
val: true,
|
||||
span: diff_span
|
||||
}));
|
||||
assert_eq!(set.len(), 1);
|
||||
|
||||
set.insert(HashableValue::Int { val: 2, span });
|
||||
assert_eq!(set.len(), 2);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user