Compare commits
1567 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
e94b8007c1 | |||
0c1a7459b2 | |||
384ea111eb | |||
5c94528fe2 | |||
53330c5676 | |||
1837acfc70 | |||
1dbf351425 | |||
f50f37c853 | |||
3706bef0a1 | |||
de30236f38 | |||
e1c92e90ca | |||
39f03bf5e4 | |||
e62e0fb679 | |||
89a000a572 | |||
ca6baf7a46 | |||
d603086d2f | |||
a811eee6b8 | |||
1efae6876d | |||
3522bead97 | |||
7f0921a14b | |||
b719f8d4eb | |||
29c8b826d4 | |||
ba1ff4cf6c | |||
5c83f4d405 | |||
f3c175562d | |||
c33104c4ae | |||
ef59b4aa51 | |||
3389baa392 | |||
5d3b63fa90 | |||
061c822c5d | |||
0c920f7d05 | |||
43dd0960a0 | |||
9fb12fefb0 | |||
8ba3e3570c | |||
ea6912c3f7 | |||
deeb1da359 | |||
52dba91e1a | |||
266fac910a | |||
3ad5d4af66 | |||
a93a9b9029 | |||
6a35e6b7b6 | |||
c3a16902fe | |||
fc7ed1bfe4 | |||
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 | |||
04a9c8f3fd | |||
673fe2b56a | |||
930cb26e99 | |||
3701fd1d76 | |||
ee6ab17fde | |||
486f91e3a7 | |||
906c0e6bca | |||
1336acd34a | |||
2013e9300a | |||
90ddb23492 | |||
bee7ef21eb | |||
d1d1402512 | |||
c33d082ecc | |||
6f53912655 | |||
34a8a897c5 | |||
4d7dd23779 | |||
f8e6620e48 | |||
7cbeebaac1 | |||
9d7685e565 | |||
c2aa6c708d | |||
626b1b99cd | |||
4103abc685 | |||
d0119ea05d | |||
3df5e63c05 | |||
e77c6bb284 | |||
95841e3489 | |||
c2c4a1968b | |||
2e2d5ef0eb | |||
7a892ec5d7 | |||
865906e450 | |||
7319b6b168 | |||
c3b6e07de6 | |||
5c27ffa42e | |||
3dc19d4179 | |||
a8e5cb871e | |||
a7a213b3f2 | |||
512dcf0988 | |||
8d027a0617 | |||
11a781fc36 | |||
a42bbea98d | |||
c8b9913718 | |||
1fd26727c5 | |||
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 | |||
ff673ba0ba | |||
f57d629b55 | |||
43972db131 | |||
f2aa952e86 | |||
071066b6d9 | |||
ac2afab40b | |||
99de2b1d77 | |||
45eba8b922 | |||
56307553ae | |||
2bbba3f5da | |||
34e0fd622b | |||
124561ff12 | |||
d8c721282b | |||
d2a1564b94 | |||
7cf96c6597 | |||
b8f1fea7fe | |||
3916ac4165 | |||
c17e1473db | |||
21ddfc61f4 | |||
ce4d9dc7c6 | |||
414ed4877a | |||
5de12da765 | |||
bab8f6bd28 | |||
ee239a0d37 | |||
e07ce57423 | |||
6d58e2b51e | |||
c8b16c14d5 | |||
e1e7e94261 | |||
8c0fa0d26e | |||
f7f8b0dbff | |||
63c3d19c67 | |||
0ba0daa2c4 | |||
5d88ed6c75 | |||
f052b3313d | |||
8043516d75 | |||
6a1942b18f | |||
76019f434e | |||
a2aaeb38ed | |||
143855b662 | |||
d30dfc63c4 | |||
250743f60f | |||
00aac850fd | |||
e01e73cb67 | |||
ff43ca4d24 | |||
88988dc9f4 | |||
aa7226d5f6 | |||
adb7eeb740 | |||
96bdcc4ff7 | |||
f8f437b060 | |||
b35914bd17 | |||
6fbe02eb21 | |||
5459d30a24 | |||
ab22619f4a | |||
42367ddf6d | |||
e324c1a078 | |||
4fd020ab7f | |||
50cbd16ec7 | |||
be827e5628 | |||
f1b2ab0b27 | |||
0f107b2830 | |||
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 | |||
345b51b272 | |||
5837cdb3f1 | |||
183d200b9f | |||
8c43f60e2e | |||
f8e48aa0af | |||
44fad9e698 | |||
14f30287f1 | |||
ae1109139d | |||
1d356276c2 | |||
4a1df604c9 | |||
d23929fc80 | |||
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 | |||
e549c1112b | |||
da515b1c9d | |||
37f7a36123 | |||
9838154ad1 | |||
f301f686b5 | |||
2dcfecbbd7 | |||
751595e72e | |||
a160d480b1 | |||
cf3f3fde92 | |||
6e6df46469 | |||
624edce4f7 | |||
51e48bee53 | |||
d853127c2e | |||
520d9e1fb6 | |||
37150af970 | |||
bac8b8a450 | |||
40ad9acbc3 | |||
1308eb45d5 | |||
f92e9d25a5 | |||
4fc533340b | |||
c998bbbe6d | |||
c114f41545 | |||
9baf720156 | |||
4b31fe1924 | |||
656e86a7ca | |||
5d62f1a9c1 | |||
6d6b850911 | |||
b5329fe4ec | |||
78256b4923 | |||
bd6c550470 | |||
95628bef16 | |||
ca7ff37697 | |||
af02c8f6ea | |||
0f27249319 | |||
1eb93e5c07 | |||
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 | |||
51bea2e884 | |||
b1d7e3aa49 | |||
1c52919103 | |||
b322a12f58 | |||
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 | |||
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 | |||
5021d61800 | |||
06d819ecc8 | |||
bb126e8e09 | |||
248decc546 | |||
2500f23fcb | |||
7eb022b58c | |||
d481d5ca96 | |||
996ee363b7 | |||
011ad2e4e6 | |||
d6d0bad7aa | |||
b35d47c500 | |||
b3b51a2ed6 | |||
c7de1ee13a | |||
cc8a470668 | |||
74d4c501a8 | |||
5cc7fbcde7 | |||
48f534cd3b | |||
8536c12bd9 | |||
8dc3ebd6e2 | |||
5da1310696 | |||
9d49618e87 | |||
7697f7bdce | |||
51a43f5617 | |||
11b40a6c31 | |||
3c843f7f61 | |||
e402adbba0 | |||
c4ea398160 | |||
27dcbe5c8a | |||
4eb43adef2 | |||
0ef0588e29 | |||
80e7a8d594 | |||
1b96da5e5b | |||
e3ce58475b | |||
31ce8c1e33 | |||
14426433aa | |||
535ece4e76 | |||
75ec0d123a | |||
c884d5ca31 | |||
f80e9d4b60 | |||
26166192e5 | |||
7c2bf68d45 | |||
6f5f1fa43a | |||
58b0e571d3 | |||
a88058006a | |||
1e1e12b027 | |||
9737d4a614 | |||
0fe525de87 | |||
4dacfaa44a | |||
b2148e32b8 | |||
e325fd114d | |||
dfd321a679 | |||
909b7d2160 | |||
2b5cc63118 | |||
75e323ee35 | |||
758fce8ae3 | |||
91090e1db1 | |||
5bf51b5a7a | |||
1d7ab28a0f | |||
eba3484611 | |||
b5ec9e0360 | |||
0cc121876b | |||
e4e1b7a11e | |||
be68b84473 | |||
ae34a34e00 | |||
81cd03626d | |||
6f4df31927 | |||
03339beae1 | |||
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 | |||
5a5205d5d9 | |||
503939dcbe | |||
000db46618 | |||
d6e24cceb4 | |||
99666829e0 | |||
db3e9efc4b | |||
3e232a5db8 | |||
e00755a2e9 | |||
d34e083976 | |||
8250b44ce5 | |||
f0d5e2dcf1 | |||
125c8c82c3 | |||
2b5ef1b2d7 | |||
719920fa37 | |||
3b134a1ae2 | |||
84d0e0a059 | |||
0a48bc973d | |||
0108a935ed | |||
5ccbf4df67 | |||
9ee4dc49ee | |||
756269ee8d | |||
abb0d7bd22 | |||
47421e9ca7 | |||
3f8f3ecf9a | |||
f57f7b2def | |||
9e176674a5 | |||
57a07385ac | |||
12cf1a8f83 | |||
e9f1575924 | |||
1015ea814c | |||
abac7e3795 | |||
22c6ed4718 | |||
3421a8b58b | |||
75510b172a | |||
04a8280d51 | |||
139775dcce | |||
d9c42eb194 | |||
6387401041 | |||
dadc354847 | |||
25a776c36b | |||
637e4f6e6d | |||
b12a265f1e | |||
cf60f72452 | |||
a176f12c9e | |||
a2996abd47 | |||
b8d218e65b | |||
d1da75d315 | |||
767d822cbf | |||
b4977f1515 | |||
6c589affe7 | |||
cb9db792a6 | |||
04990eeba4 | |||
772f8598dd | |||
95439f5e9c | |||
36c32e9832 | |||
660e8b5b73 | |||
5d442a287f | |||
984538555c | |||
0ccbebee7a | |||
9f9bec38e1 | |||
d1474c0691 | |||
673137be8b | |||
180dafb84c | |||
2553da3dc4 | |||
a7ecf7af90 | |||
ff1adbd346 | |||
32f39c2fb8 | |||
923330aadd | |||
c87414e462 | |||
dbfd2808ba | |||
3c18cac134 | |||
4841d62d76 | |||
e5aa8b9d3f | |||
a1d6cefdf8 | |||
d532f3f304 | |||
29771c7d23 | |||
cb0914ecb0 | |||
672dd5a868 | |||
cbe85cbeaf | |||
6731e3542d | |||
5a6aebfcb2 | |||
96af23f370 | |||
4e6b6a8902 | |||
bafc50fd5c | |||
c03324ec9c | |||
1a9247b77f | |||
22e30d5ea7 | |||
b4f918b889 | |||
7b54e5c4ab | |||
c4d1c458a2 | |||
7aa1d8ac2a | |||
b6fdf611f6 | |||
c0bad7ab23 | |||
d7a3c7522b | |||
4dfde7393b | |||
32c1f0c8d4 | |||
eb67eab122 | |||
d88e46d2d1 | |||
caa6236f1f | |||
f459f77335 | |||
66c58217af | |||
8f07f40f22 | |||
e6a2e27e33 | |||
8577d3ff41 | |||
78054a5352 | |||
ce0b5bf4ab | |||
9936946eb5 | |||
013b12a864 | |||
2f04c172fe | |||
648fe052db | |||
55aa70c88a | |||
aa7ebdc9ce | |||
9c98783917 | |||
4b8ba29cdb | |||
4749776984 | |||
47ee50072e | |||
198c884158 | |||
1d945d8ce3 | |||
2d3a56f0d3 | |||
bfd05772ef | |||
9a16a8fd06 | |||
2ea19aeac0 | |||
0794ebf5fa | |||
a8ba00b250 | |||
26d50ebcd5 | |||
0694245ccd | |||
c1194b3d1e | |||
16baf5e16a | |||
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 | |||
2794556eaa | |||
a26c42a9b6 | |||
331ccd544f | |||
d6b1ff932a | |||
26b1f022b7 | |||
ab307c8d38 | |||
a3d4794341 | |||
25c7d8ead6 | |||
2834d71a12 | |||
bf9b6d8088 | |||
d9cff4238d | |||
198a36b744 | |||
f259992b4b | |||
ca8d311c78 | |||
acc035dbef | |||
5e33b8536b | |||
74bb2af3e1 | |||
b20c4047d4 | |||
82cf6caba4 | |||
bc3f820227 | |||
12d80c2732 | |||
6c0ce95d0f | |||
750502c870 | |||
df63490266 | |||
7c8504ea24 | |||
94687a7603 | |||
e1be8f61fc | |||
3d252a9797 | |||
45683a53c9 | |||
c4c4d82bf4 | |||
4ed79614ac | |||
73f6a57b12 | |||
fcc1cd3d57 | |||
5da2ab1b7d | |||
d0be193307 | |||
b3fb106cce | |||
46d2efca13 | |||
24cd1b591c | |||
bb9e6731ea | |||
5dd5a89775 | |||
2f91aca897 | |||
35c3622405 | |||
8ab7b27d4f | |||
9e76fb2231 | |||
9c7d2ab8f2 | |||
739425431a | |||
dda6554990 | |||
2f43cc353b | |||
ceea7e5aeb | |||
579814895d | |||
7655b070df | |||
1355a5dd33 | |||
f62e3119c4 | |||
828585a312 | |||
ef4af443a5 | |||
1a3e1e0959 | |||
40004e64a6 | |||
50dc0ad207 | |||
3da4f02ffa | |||
8a2bba4efb | |||
1ba80224ad | |||
bf19918e3c | |||
38fef28c84 | |||
273f964293 | |||
d2577acccd | |||
d92e661253 | |||
cb11f042ab | |||
b82a4869d5 | |||
c2be740ad4 | |||
61258d03ad | |||
79a05d63c8 | |||
18752672d0 | |||
cdc37bb142 | |||
083dcd4541 | |||
b6f00d07e8 | |||
b0ffaf1c91 | |||
2af61bd07e | |||
1caae90c02 | |||
184125a70a | |||
7cac5bb633 | |||
53314cb8b2 | |||
b5e287e065 | |||
2eeceae613 | |||
a8366ebf15 | |||
ad48387aa0 | |||
a4bcc1ff3d | |||
fca3a6b75e | |||
6fcdc76059 | |||
3eefa6dec8 | |||
8c6feb7e80 | |||
37f8ff0efc | |||
07c22c7e81 | |||
1ac0c0bfc5 | |||
c25209eb34 | |||
4deed7c836 | |||
92f72b4103 | |||
30f54626d3 | |||
3a8206d1fb | |||
6b0b8744c1 | |||
0b8352049c | |||
c03f700662 | |||
d08f2e73d0 | |||
aa7f23e1e1 | |||
4249c5b3e0 | |||
6f1a5c8e02 | |||
03a93bd089 | |||
6aef00ecff | |||
949c6a5932 | |||
7922bb4020 | |||
697bf16f26 | |||
9916f35b22 | |||
0a6f62bc0e | |||
bc974a3e7d | |||
1aa70c50aa | |||
134b45dc03 | |||
96c0b933d9 | |||
7b51c5c49f | |||
eac02b55f6 | |||
5d4ae4a2a4 | |||
04cbef3aa8 | |||
e540f0ad26 | |||
bf1a23afcf | |||
04a6a4f860 | |||
666bee61f7 | |||
a6e0f0bb74 | |||
03ce896f6f | |||
80e0cd4e00 | |||
049477a9bd | |||
d644a8d41f | |||
e0c2074ed5 | |||
d8bf48e692 | |||
a91efc3cbd | |||
fb42c94b79 | |||
ba2e3d94eb | |||
4ef65f0983 | |||
2675ad9304 | |||
c1240f214c | |||
7f3eab418f | |||
4f89ed5d66 | |||
43fd0b6ae9 | |||
e3abadd686 | |||
3d2e227f11 | |||
29d2449fb3 |
@ -1,66 +0,0 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux-stable:
|
||||
image: ubuntu-18.04
|
||||
style: 'unflagged'
|
||||
linux-minimal:
|
||||
image: ubuntu-18.04
|
||||
style: 'minimal'
|
||||
linux-extra:
|
||||
image: ubuntu-18.04
|
||||
style: 'extra'
|
||||
linux-wasm:
|
||||
image: ubuntu-18.04
|
||||
style: 'wasm'
|
||||
macos-stable:
|
||||
image: macOS-10.15
|
||||
style: 'unflagged'
|
||||
windows-stable:
|
||||
image: windows-2019
|
||||
style: 'unflagged'
|
||||
fmt:
|
||||
image: ubuntu-18.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
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
echo "Installing clippy"
|
||||
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
fi
|
||||
# rustup update
|
||||
# rustc -Vv
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used -A clippy::needless_collect
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: cd samples/wasm && npm install wasm-pack && node ./node_modules/wasm-pack/run.js build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
@ -1,6 +1,13 @@
|
||||
# use LLD as linker on Windows because it could be faster
|
||||
# for full and incremental builds compared to default
|
||||
|
||||
# increase the default windows stack size
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
#linker = "lld-link.exe"
|
||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
||||
|
||||
# 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 main
|
||||
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:
|
||||
- main
|
||||
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: /^\d+\.\d+\.\d+$/
|
||||
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 main (doesn't build --release)
|
||||
build_with_deploy_devel:
|
||||
jobs:
|
||||
|
||||
# Deploy devel tag on merge to main
|
||||
- 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: main
|
||||
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:
|
||||
- main
|
||||
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 +0,0 @@
|
||||
target
|
@ -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
|
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -38,9 +38,9 @@ body:
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: "Please run `version | pivot key value | to md --pretty` and paste the output to show OS, features, etc."
|
||||
description: "Please run `version | transpose key value | to md --pretty` and paste the output to show OS, features, etc."
|
||||
placeholder: |
|
||||
> version | pivot key value | to md --pretty
|
||||
> version | transpose key value | to md --pretty
|
||||
| key | value |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| version | 0.40.0 |
|
||||
|
11
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Description
|
||||
|
||||
(description of your pull request here)
|
||||
|
||||
# Tests
|
||||
|
||||
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 --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- [ ] `cargo build; cargo test --all --all-features` to check that all the tests pass
|
114
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
on: [pull_request]
|
||||
|
||||
name: Continuous integration
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
style: [all, default, minimal]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: all
|
||||
flags: '--all-features'
|
||||
- style: default
|
||||
flags: ''
|
||||
- style: minimal
|
||||
flags: '--no-default-features'
|
||||
exclude:
|
||||
- platform: windows-latest
|
||||
style: default
|
||||
- platform: windows-latest
|
||||
style: minimal
|
||||
- platform: macos-latest
|
||||
style: default
|
||||
- platform: macos-latest
|
||||
style: minimal
|
||||
|
||||
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
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
- name: Build Nushell
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: ${{ matrix.flags }}
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all ${{ matrix.flags }}
|
||||
|
||||
|
||||
python-virtualenv:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
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
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install Nushell
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --path=. --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/kubouch/virtualenv.git && \
|
||||
cd virtualenv && \
|
||||
git checkout engine-q-update
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
|
||||
shell: bash
|
8
.github/workflows/release.yml
vendored
@ -92,8 +92,6 @@ jobs:
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
@ -188,8 +186,6 @@ jobs:
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -215,7 +211,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-wix
|
||||
args: cargo-wix --version 0.3.1
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
@ -291,8 +287,6 @@ jobs:
|
||||
cp target\release\nu.exe output\
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
|
18
.gitpod.Dockerfile
vendored
@ -1,18 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
|
||||
# To force a rebuild, simply increase this counter:
|
||||
ENV TRIGGER_REBUILD 2
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libssl-dev \
|
||||
libxcb-composite0-dev \
|
||||
pkg-config \
|
||||
libpython3.6 \
|
||||
rust-lldb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUST_LLDB=/usr/bin/lldb-11
|
25
.gitpod.yml
@ -1,25 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
tasks:
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
init: cargo install --path . --features=stable
|
||||
command: nu
|
||||
github:
|
||||
prebuilds:
|
||||
branches: true
|
||||
pullRequestsFromForks: true
|
||||
addLabel: prebuilt-in-gitpod
|
||||
vscode:
|
||||
extensions:
|
||||
- hbenl.vscode-test-explorer@2.15.0:koqDUMWDPJzELp/hdS/lWw==
|
||||
- Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw==
|
||||
- serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw==
|
||||
- belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ==
|
||||
- bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw==
|
||||
- webfreak.debug@0.24.0:1zVcRsAhewYEX3/A9xjMNw==
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"name": "Debug Rust Code",
|
||||
"preLaunchTask": "cargo",
|
||||
"target": "${workspaceFolder}/target/debug/nu",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"valuesFormatting": "parseText"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"type": "process",
|
||||
"label": "cargo",
|
||||
}
|
||||
],
|
||||
}
|
3236
Cargo.lock
generated
262
Cargo.toml
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
@ -10,218 +10,94 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.42.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
version = "0.60.0"
|
||||
|
||||
# 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-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.42.0", path="./crates/nu-cli", default-features=false }
|
||||
nu-command = { version = "0.42.0", path="./crates/nu-command" }
|
||||
nu-completion = { version = "0.42.0", path="./crates/nu-completion" }
|
||||
nu-data = { version = "0.42.0", path="./crates/nu-data" }
|
||||
nu-engine = { version = "0.42.0", path="./crates/nu-engine" }
|
||||
nu-errors = { version = "0.42.0", path="./crates/nu-errors" }
|
||||
nu-parser = { version = "0.42.0", path="./crates/nu-parser" }
|
||||
nu-path = { version = "0.42.0", path="./crates/nu-path" }
|
||||
nu-plugin = { version = "0.42.0", path="./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.42.0", path="./crates/nu-protocol" }
|
||||
nu-source = { version = "0.42.0", path="./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.42.0", path="./crates/nu-value-ext" }
|
||||
|
||||
nu_plugin_binaryview = { version = "0.42.0", path="./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_chart = { version = "0.42.0", path="./crates/nu_plugin_chart", optional=true }
|
||||
nu_plugin_from_bson = { version = "0.42.0", path="./crates/nu_plugin_from_bson", optional=true }
|
||||
nu_plugin_from_sqlite = { version = "0.42.0", path="./crates/nu_plugin_from_sqlite", optional=true }
|
||||
nu_plugin_inc = { version = "0.42.0", path="./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.42.0", path="./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_query_json = { version = "0.42.0", path="./crates/nu_plugin_query_json", optional=true }
|
||||
nu_plugin_s3 = { version = "0.42.0", path="./crates/nu_plugin_s3", optional=true }
|
||||
nu_plugin_selector = { version = "0.42.0", path="./crates/nu_plugin_selector", optional=true }
|
||||
nu_plugin_start = { version = "0.42.0", path="./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_textview = { version = "0.42.0", path="./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_to_bson = { version = "0.42.0", path="./crates/nu_plugin_to_bson", optional=true }
|
||||
nu_plugin_to_sqlite = { version = "0.42.0", path="./crates/nu_plugin_to_sqlite", optional=true }
|
||||
nu_plugin_tree = { version = "0.42.0", path="./crates/nu_plugin_tree", optional=true }
|
||||
nu_plugin_xpath = { version = "0.42.0", path="./crates/nu_plugin_xpath", optional=true }
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
chrono = "0.4.19"
|
||||
crossterm = "0.23.0"
|
||||
crossterm_winapi = "0.9.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "4.1.0"
|
||||
nu-ansi-term = "0.45.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.60.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.60.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.60.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.60.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.60.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.60.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.60.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.60.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.60.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.60.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.60.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.60.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.60.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.60.0" }
|
||||
pretty_env_logger = "0.4.0"
|
||||
rayon = "1.5.1"
|
||||
reedline = "0.3.0"
|
||||
is_executable = "1.0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.42.0", path="./crates/nu-test-support" }
|
||||
nu-test-support = { path="./crates/nu-test-support" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.10.0"
|
||||
rstest = "0.12.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[build-dependencies]
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
embed-resource = "1"
|
||||
|
||||
[features]
|
||||
fetch-support = ["nu-command/fetch", "nu-command/post"]
|
||||
sys-support = ["nu-command/sys", "nu-command/ps"]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-command/term"]
|
||||
uuid-support = ["nu-command/uuid_crate"]
|
||||
which-support = ["nu-command/which", "nu-engine/which"]
|
||||
|
||||
default = [
|
||||
"nu-cli/shadow-rs",
|
||||
"sys-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"term-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"fetch-support",
|
||||
"zip-support",
|
||||
"dataframe",
|
||||
]
|
||||
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
default = ["plugin", "which", "zip-support", "trash-support"]
|
||||
stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"inc",
|
||||
"tree",
|
||||
"textview",
|
||||
"trash-support",
|
||||
"uuid-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
"s3",
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
"query-json",
|
||||
]
|
||||
|
||||
wasi = ["inc", "match", "match", "tree", "rustyline-support"]
|
||||
extra = ["default", "dataframe"]
|
||||
wasi = []
|
||||
|
||||
# Stable (Default)
|
||||
inc = ["nu_plugin_inc"]
|
||||
match = ["nu_plugin_match"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
which = ["nu-command/which"]
|
||||
zip-support = ["nu-command/zip"]
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
query-json = ["nu_plugin_query_json"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trash-support = [
|
||||
"nu-command/trash-support",
|
||||
"nu-engine/trash-support",
|
||||
]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
zip-support = ["nu-command/zip"]
|
||||
|
||||
#dataframe feature for nushell
|
||||
dataframe = [
|
||||
"nu-engine/dataframe",
|
||||
"nu-protocol/dataframe",
|
||||
"nu-command/dataframe",
|
||||
"nu-value-ext/dataframe",
|
||||
"nu-data/dataframe",
|
||||
"nu_plugin_to_bson/dataframe",
|
||||
]
|
||||
# Dataframe feature for nushell
|
||||
dataframe = ["nu-command/dataframe"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s" # Optimize for size.
|
||||
opt-level = "s" # Optimize for size
|
||||
strip = "debuginfo"
|
||||
lto = "thin"
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
# unless we use [[bin]], so we use this as a workaround
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_textview"
|
||||
path = "src/plugins/nu_plugin_core_textview.rs"
|
||||
required-features = ["textview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_inc"
|
||||
path = "src/plugins/nu_plugin_core_inc.rs"
|
||||
required-features = ["inc"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_match"
|
||||
path = "src/plugins/nu_plugin_core_match.rs"
|
||||
required-features = ["match"]
|
||||
|
||||
# Extra plugins
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_binaryview"
|
||||
path = "src/plugins/nu_plugin_extra_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_query_json"
|
||||
path = "src/plugins/nu_plugin_extra_query_json.rs"
|
||||
required-features = ["query-json"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_bar"
|
||||
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_line"
|
||||
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_xpath"
|
||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||
required-features = ["xpath"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_selector"
|
||||
path = "src/plugins/nu_plugin_extra_selector.rs"
|
||||
required-features = ["selector"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_bson"
|
||||
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_bson"
|
||||
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
# build with `cargo build --profile profiling`
|
||||
# to analyze performance with tooling like linux perf
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
|
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2021 Nushell Project
|
||||
Copyright (c) 2019 - 2021 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
|
||||
|
49
README.md
@ -1,7 +1,7 @@
|
||||
# README
|
||||
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
|
||||
[](https://github.com/nushell/nushell/actions)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
## Status
|
||||
|
||||
@ -37,10 +37,6 @@ We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https
|
||||
|
||||
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
Try it in Gitpod.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
|
||||
## Installation
|
||||
|
||||
### Local
|
||||
@ -61,6 +57,8 @@ Optional dependencies:
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
For Windows users, you may also need to install the [Microsoft Visual C++ 2015 Redistributables](https://docs.microsoft.com/cpp/windows/latest-supported-vc-redist).
|
||||
|
||||
```shell
|
||||
cargo install nu
|
||||
```
|
||||
@ -71,6 +69,12 @@ To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
||||
winget install nushell
|
||||
```
|
||||
|
||||
To install Nu via the [Chocolatey](https://chocolatey.org) package manager:
|
||||
|
||||
```shell
|
||||
choco install nushell
|
||||
```
|
||||
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```shell
|
||||
@ -113,12 +117,11 @@ Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing lef
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
||||
3 │ docker │ Dir │ 288 B │ 3 months ago
|
||||
4 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||
5 │ images │ Dir │ 160 B │ 5 months ago
|
||||
6 │ src │ Dir │ 128 B │ 1 day ago
|
||||
7 │ target │ Dir │ 160 B │ 5 days ago
|
||||
8 │ tests │ Dir │ 192 B │ 3 months ago
|
||||
3 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||
4 │ images │ Dir │ 160 B │ 5 months ago
|
||||
5 │ src │ Dir │ 128 B │ 1 day ago
|
||||
6 │ target │ Dir │ 160 B │ 5 days ago
|
||||
7 │ tests │ Dir │ 192 B │ 3 months ago
|
||||
───┴────────┴──────┴───────┴──────────────
|
||||
```
|
||||
|
||||
@ -248,19 +251,19 @@ Nu is in heavy development and will naturally change as it matures and people us
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | | X | | | Aliases allow for shortening large commands, while passing flags |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
## Officially Supported By
|
||||
|
BIN
assets/icons/black-white.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
assets/icons/daniella-eth.png
Normal file
After Width: | Height: | Size: 206 KiB |
BIN
assets/icons/green-black.png
Normal file
After Width: | Height: | Size: 167 KiB |
BIN
assets/icons/green-white-black-circle.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
assets/icons/green-white2.png
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
assets/icons/green-white6.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
assets/icons/green-white7.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
assets/icons/nu-cascadia-tiffany.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/icons/nu-kfarmer.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/icons/nushell-original.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/icons/nushell-round-black.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/icons/nushell-round-black2.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
assets/icons/nushell-round-black4.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
assets/nu_logo.ico
Normal file
After Width: | Height: | Size: 6.0 KiB |
49
assets/nushell.rc
Normal file
@ -0,0 +1,49 @@
|
||||
#include <winver.h>
|
||||
|
||||
#define VER_FILEVERSION 0,59,1,0
|
||||
#define VER_FILEVERSION_STR "0.59.1"
|
||||
|
||||
#define VER_PRODUCTVERSION 0,59,1,0
|
||||
#define VER_PRODUCTVERSION_STR "0.59.1"
|
||||
|
||||
#ifdef RC_INVOKED
|
||||
|
||||
#ifdef DEBUG // TODO: Actually define DEBUG
|
||||
#define VER_DEBUG VS_FF_DEBUG
|
||||
#else
|
||||
#define VER_DEBUG 0
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_FILEVERSION
|
||||
PRODUCTVERSION VER_PRODUCTVERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS VER_DEBUG
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "nushell"
|
||||
VALUE "FileDescription", "Nushell"
|
||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
||||
VALUE "InternalName", "nu.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2022"
|
||||
VALUE "OriginalFilename", "nu.exe"
|
||||
VALUE "ProductName", "Nushell"
|
||||
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
#define IDI_ICON 0x101
|
||||
IDI_ICON ICON "assets/nu_logo.ico"
|
||||
#endif
|
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
@ -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
@ -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
|
||||
}
|
7
build.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
embed_resource::compile_for("assets/nushell.rc", &["nu"])
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
2
crates/nu-ansi-term/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
target
|
||||
Cargo.lock
|
@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = [
|
||||
"ogham@bsago.me",
|
||||
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
|
||||
"Josh Triplett <josh@joshtriplett.org>",
|
||||
"The Nu Project Contributors",
|
||||
]
|
||||
description = "Library for ANSI terminal colors and styles (bold, underline)"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-ansi-term"
|
||||
version = "0.42.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
# name = "nu-ansi-term"
|
||||
|
||||
[features]
|
||||
derive_serde_style = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
overload = "0.1.1"
|
||||
serde = { version="1.0.90", features=["derive"], optional=true }
|
||||
|
||||
# [dependencies.serde]
|
||||
# version = "1.0.90"
|
||||
# features = ["derive"]
|
||||
# optional = true
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
regex = "1.1.9"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.39"
|
@ -1,182 +0,0 @@
|
||||
# nu-ansi-term
|
||||
|
||||
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
|
||||
|
||||
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
nu_ansi_term = "0.13"
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
|
||||
|
||||
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
|
||||
The `Color` enum represents the available colors.
|
||||
And an `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
`Color` is also available as an alias to `Color`.
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
|
||||
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
|
||||
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
|
||||
|
||||
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
let red_string = Red.paint("a red string").to_string();
|
||||
```
|
||||
|
||||
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
|
||||
|
||||
```rust,ignore
|
||||
let enabled = nu_ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
|
||||
Each method creates a new style that has that specific property set.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
|
||||
println!("How about some {} and {}?",
|
||||
Style::new().bold().paint("bold"),
|
||||
Style::new().underline().paint("underline"));
|
||||
```
|
||||
|
||||
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::{Blue, Yellow};
|
||||
|
||||
println!("Demonstrating {} and {}!",
|
||||
Blue.bold().paint("blue bold"),
|
||||
Yellow.underline().paint("yellow underline"));
|
||||
|
||||
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
```
|
||||
|
||||
The complete list of styles you can use are:
|
||||
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
|
||||
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
```
|
||||
|
||||
You can turn a `Color` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
## Extended colors
|
||||
|
||||
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
|
||||
This can be included wherever you would use a `Color`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Fixed;
|
||||
|
||||
Fixed(134).paint("A sort of light purple");
|
||||
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
```
|
||||
|
||||
The first sixteen of these values are the same as the normal and bold standard color variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colors instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::RGB;
|
||||
|
||||
RGB(70, 130, 180).paint("Steel blue");
|
||||
```
|
||||
|
||||
## Combining successive coloured strings
|
||||
|
||||
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
|
||||
For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
|
||||
|
||||
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
|
||||
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
|
||||
|
||||
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
|
||||
let some_value = format!("{:b}", 42);
|
||||
let strings: &[ANSIString<'static>] = &[
|
||||
Red.paint("["),
|
||||
Red.bold().paint(some_value),
|
||||
Red.paint("]"),
|
||||
];
|
||||
|
||||
println!("Value: {}", ANSIStrings(strings));
|
||||
```
|
||||
|
||||
There are several things to note here.
|
||||
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
|
||||
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
|
||||
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
|
||||
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
|
||||
|
||||
## Byte strings
|
||||
|
||||
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
|
||||
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
|
||||
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
|
||||
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
||||
|
||||
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
use nu_ansi_term::ANSIByteStrings;
|
||||
|
||||
ANSIByteStrings(&[
|
||||
Green.paint("user data 1\n".as_bytes()),
|
||||
Green.bold().paint("user data 2\n".as_bytes()),
|
||||
]).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
@ -1,72 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
// This example prints out the 256 colors.
|
||||
// They're arranged like this:
|
||||
//
|
||||
// - 0 to 8 are the eight standard colors.
|
||||
// - 9 to 15 are the eight bold colors.
|
||||
// - 16 to 231 are six blocks of six-by-six color squares.
|
||||
// - 232 to 255 are shades of grey.
|
||||
|
||||
fn main() {
|
||||
// First two lines
|
||||
for c in 0..8 {
|
||||
glow(c, c != 0);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 8..16 {
|
||||
glow(c, c != 8);
|
||||
print!(" ");
|
||||
}
|
||||
println!("\n");
|
||||
|
||||
// Six lines of the first three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(16 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// Six more lines of the other three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(124 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// The last greyscale lines
|
||||
for c in 232..=243 {
|
||||
glow(c, false);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 244..=255 {
|
||||
glow(c, true);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn glow(c: u8, light_bg: bool) {
|
||||
let base = if light_bg { Color::Black } else { Color::White };
|
||||
let style = base.on(Color::Fixed(c));
|
||||
print!("{}", style.paint(&format!(" {:3} ", c)));
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color::*, Style};
|
||||
|
||||
// This example prints out the 16 basic colors.
|
||||
|
||||
fn main() {
|
||||
let normal = Style::default();
|
||||
|
||||
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
|
||||
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
|
||||
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
|
||||
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
|
||||
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
|
||||
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
|
||||
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
|
||||
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
|
||||
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
|
||||
|
||||
fn main() {
|
||||
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
|
||||
|
||||
// a gradient from hex colors
|
||||
let start = Rgb::from_hex(0x40c9ff);
|
||||
let end = Rgb::from_hex(0xe81cff);
|
||||
let grad0 = Gradient::new(start, end);
|
||||
|
||||
// a gradient from color::rgb()
|
||||
let start = Color::Rgb(64, 201, 255);
|
||||
let end = Color::Rgb(232, 28, 255);
|
||||
let gradient = Gradient::from_color_rgb(start, end);
|
||||
|
||||
// a slightly different gradient
|
||||
let start2 = Color::Rgb(128, 64, 255);
|
||||
let end2 = Color::Rgb(0, 28, 255);
|
||||
let gradient2 = Gradient::from_color_rgb(start2, end2);
|
||||
|
||||
// reverse the gradient
|
||||
let gradient3 = gradient.reverse();
|
||||
|
||||
let build_fg = gradient.build(text, TargetGround::Foreground);
|
||||
println!("{}", build_fg);
|
||||
let build_bg = gradient.build(text, TargetGround::Background);
|
||||
println!("{}", build_bg);
|
||||
let bgt = build_all_gradient_text(text, gradient, gradient2);
|
||||
println!("{}", bgt);
|
||||
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
|
||||
println!("{}", bgt2);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
grad0.build("nushell is awesome", TargetGround::Foreground)
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
// This example prints out a color gradient in a grid by calculating each
|
||||
// character’s red, green, and blue components, and using 24-bit color codes
|
||||
// to display them.
|
||||
|
||||
const WIDTH: i32 = 80;
|
||||
const HEIGHT: i32 = 24;
|
||||
|
||||
fn main() {
|
||||
for row in 0..HEIGHT {
|
||||
for col in 0..WIDTH {
|
||||
let r = (row * 255 / HEIGHT) as u8;
|
||||
let g = (col * 255 / WIDTH) as u8;
|
||||
let b = 128;
|
||||
|
||||
print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" "));
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
@ -1,405 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::fmt;
|
||||
|
||||
impl Style {
|
||||
/// Write any bytes that go *before* a piece of text to the given writer.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
// If there are actually no styles here, then don’t write *any* codes
|
||||
// as the prefix. An empty ANSI code may not affect the terminal
|
||||
// output at all, but a user may just want a code-free string.
|
||||
if self.is_plain() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Write the codes’ prefix, then write numbers, separated by
|
||||
// semicolons, for each text style we want to apply.
|
||||
write!(f, "\x1B[")?;
|
||||
let mut written_anything = false;
|
||||
|
||||
{
|
||||
let mut write_char = |c| {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
write!(f, "{}", c)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if self.is_bold {
|
||||
write_char('1')?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_char('2')?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_char('3')?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_char('4')?
|
||||
}
|
||||
if self.is_blink {
|
||||
write_char('5')?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_char('7')?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_char('8')?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_char('9')?
|
||||
}
|
||||
}
|
||||
|
||||
// The foreground and background colors, if specified, need to be
|
||||
// handled specially because the number codes are more complicated.
|
||||
// (see `write_background_code` and `write_foreground_code`)
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
bg.write_background_code(f)?;
|
||||
}
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
fg.write_foreground_code(f)?;
|
||||
}
|
||||
|
||||
// All the codes end with an `m`, because reasons.
|
||||
write!(f, "m")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write any bytes that go *after* a piece of text to the given writer.
|
||||
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
if self.is_plain() {
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "{}", RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The code to send to reset all styles and return to `Style::default()`.
|
||||
pub static RESET: &str = "\x1B[0m";
|
||||
|
||||
impl Color {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match self {
|
||||
Color::Black => write!(f, "30"),
|
||||
Color::Red => write!(f, "31"),
|
||||
Color::Green => write!(f, "32"),
|
||||
Color::Yellow => write!(f, "33"),
|
||||
Color::Blue => write!(f, "34"),
|
||||
Color::Purple => write!(f, "35"),
|
||||
Color::Magenta => write!(f, "35"),
|
||||
Color::Cyan => write!(f, "36"),
|
||||
Color::White => write!(f, "37"),
|
||||
Color::Fixed(num) => write!(f, "38;5;{}", num),
|
||||
Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", r, g, b),
|
||||
Color::DarkGray => write!(f, "90"),
|
||||
Color::LightRed => write!(f, "91"),
|
||||
Color::LightGreen => write!(f, "92"),
|
||||
Color::LightYellow => write!(f, "93"),
|
||||
Color::LightBlue => write!(f, "94"),
|
||||
Color::LightPurple => write!(f, "95"),
|
||||
Color::LightMagenta => write!(f, "95"),
|
||||
Color::LightCyan => write!(f, "96"),
|
||||
Color::LightGray => write!(f, "97"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match self {
|
||||
Color::Black => write!(f, "40"),
|
||||
Color::Red => write!(f, "41"),
|
||||
Color::Green => write!(f, "42"),
|
||||
Color::Yellow => write!(f, "43"),
|
||||
Color::Blue => write!(f, "44"),
|
||||
Color::Purple => write!(f, "45"),
|
||||
Color::Magenta => write!(f, "45"),
|
||||
Color::Cyan => write!(f, "46"),
|
||||
Color::White => write!(f, "47"),
|
||||
Color::Fixed(num) => write!(f, "48;5;{}", num),
|
||||
Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", r, g, b),
|
||||
Color::DarkGray => write!(f, "100"),
|
||||
Color::LightRed => write!(f, "101"),
|
||||
Color::LightGreen => write!(f, "102"),
|
||||
Color::LightYellow => write!(f, "103"),
|
||||
Color::LightBlue => write!(f, "104"),
|
||||
Color::LightPurple => write!(f, "105"),
|
||||
Color::LightMagenta => write!(f, "105"),
|
||||
Color::LightCyan => write!(f, "106"),
|
||||
Color::LightGray => write!(f, "107"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::infix`](struct.Style.html#method.infix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
impl Style {
|
||||
/// The prefix bytes for this style. These are the bytes that tell the
|
||||
/// terminal to use a different color or font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Blue};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Blue.bold();
|
||||
/// assert_eq!("\x1b[1;34m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.prefix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix bytes between this style and `next` style. These are the bytes
|
||||
/// that tell the terminal to change the style to `next`. These may include
|
||||
/// a reset followed by the next color and style, depending on the two styles.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[32m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Green.normal();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.infix(style).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Style) -> Infix {
|
||||
Infix(self, next)
|
||||
}
|
||||
|
||||
/// The suffix for this style. These are the bytes that tell the terminal
|
||||
/// to reset back to its normal color and font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Green.normal().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// The prefix bytes for this color as a `Style`. These are the bytes
|
||||
/// that tell the terminal to use a different color or font style.
|
||||
///
|
||||
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Green;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Green.suffix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix bytes between this color and `next` color. These are the bytes
|
||||
/// that tell the terminal to use the `next` color, or to do nothing if
|
||||
/// the two colors are equal.
|
||||
///
|
||||
/// See also [`Style::infix`](struct.Style.html#method.infix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::{Red, Yellow};
|
||||
///
|
||||
/// assert_eq!("\x1b[33m",
|
||||
/// Red.infix(Yellow).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Color) -> Infix {
|
||||
Infix(self.normal(), next.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this color as a `Style`. These are the bytes that
|
||||
/// tell the terminal to reset back to its normal color and font style.
|
||||
///
|
||||
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Purple;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Purple.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self.normal())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use crate::difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
}
|
||||
Difference::Reset => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.1.prefix())
|
||||
}
|
||||
Difference::Empty => {
|
||||
Ok(()) // nothing to write
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $style: expr; $input: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($style.paint($input).to_string(), $result.to_string());
|
||||
|
||||
let mut v = Vec::new();
|
||||
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
|
||||
assert_eq!(v.as_slice(), $result.as_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(plain: Style::default(); "text/plain" => "text/plain");
|
||||
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
|
||||
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
|
||||
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
|
||||
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
|
||||
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
|
||||
test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
|
||||
test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
|
||||
test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
|
||||
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
|
||||
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
|
||||
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
|
||||
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
|
||||
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
|
||||
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
|
||||
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
|
||||
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
|
||||
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
|
||||
|
||||
#[test]
|
||||
fn test_infix() {
|
||||
assert_eq!(
|
||||
Style::new().dimmed().infix(Style::new()).to_string(),
|
||||
"\x1B[0m"
|
||||
);
|
||||
assert_eq!(
|
||||
White.dimmed().infix(White.normal()).to_string(),
|
||||
"\x1B[0m\x1B[37m"
|
||||
);
|
||||
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
|
||||
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
|
||||
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use crate::style::Style;
|
||||
use std::fmt;
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use nu_ansi_term::Color::{Red, Blue};
|
||||
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
|
||||
/// format!("{:?}", Red.on(Blue).bold().italic()));
|
||||
impl fmt::Debug for Style {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
fmt.debug_struct("Style")
|
||||
.field("foreground", &self.foreground)
|
||||
.field("background", &self.background)
|
||||
.field("blink", &self.is_blink)
|
||||
.field("bold", &self.is_bold)
|
||||
.field("dimmed", &self.is_dimmed)
|
||||
.field("hidden", &self.is_hidden)
|
||||
.field("italic", &self.is_italic)
|
||||
.field("reverse", &self.is_reverse)
|
||||
.field("strikethrough", &self.is_strikethrough)
|
||||
.field("underline", &self.is_underline)
|
||||
.finish()
|
||||
} else if self.is_plain() {
|
||||
fmt.write_str("Style {}")
|
||||
} else {
|
||||
fmt.write_str("Style { ")?;
|
||||
|
||||
let mut written_anything = false;
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "fg({:?})", fg)?
|
||||
}
|
||||
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "on({:?})", bg)?
|
||||
}
|
||||
|
||||
{
|
||||
let mut write_flag = |name| {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
fmt.write_str(name)
|
||||
};
|
||||
|
||||
if self.is_blink {
|
||||
write_flag("blink")?
|
||||
}
|
||||
if self.is_bold {
|
||||
write_flag("bold")?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_flag("dimmed")?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_flag("hidden")?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_flag("italic")?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_flag("reverse")?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_flag("strikethrough")?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_flag("underline")?
|
||||
}
|
||||
}
|
||||
|
||||
write!(fmt, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $obj: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, format!("{:?}", $obj));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(empty: style() => "Style {}");
|
||||
test!(bold: style().bold() => "Style { bold }");
|
||||
test!(italic: style().italic() => "Style { italic }");
|
||||
test!(both: style().bold().italic() => "Style { bold, italic }");
|
||||
|
||||
test!(red: Red.normal() => "Style { fg(Red) }");
|
||||
test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }");
|
||||
|
||||
test!(everything:
|
||||
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
|
||||
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
|
||||
|
||||
#[test]
|
||||
fn long_and_detailed() {
|
||||
extern crate regex;
|
||||
let expected_debug = "Style { fg(Blue), bold }";
|
||||
let expected_pretty_repat = r##"(?x)
|
||||
Style\s+\{\s+
|
||||
foreground:\s+Some\(\s+
|
||||
Blue,?\s+
|
||||
\),\s+
|
||||
background:\s+None,\s+
|
||||
blink:\s+false,\s+
|
||||
bold:\s+true,\s+
|
||||
dimmed:\s+false,\s+
|
||||
hidden:\s+false,\s+
|
||||
italic:\s+false,\s+
|
||||
reverse:\s+false,\s+
|
||||
strikethrough:\s+
|
||||
false,\s+
|
||||
underline:\s+false,?\s+
|
||||
\}"##;
|
||||
let re = regex::Regex::new(expected_pretty_repat).unwrap();
|
||||
|
||||
let style = Blue.bold();
|
||||
let style_fmt_debug = format!("{:?}", style);
|
||||
let style_fmt_pretty = format!("{:#?}", style);
|
||||
println!("style_fmt_debug:\n{}", style_fmt_debug);
|
||||
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
|
||||
|
||||
assert_eq!(expected_debug, style_fmt_debug);
|
||||
assert!(re.is_match(&style_fmt_pretty));
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
use super::Style;
|
||||
|
||||
/// When printing out one colored string followed by another, use one of
|
||||
/// these rules to figure out which *extra* control codes need to be sent.
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Difference {
|
||||
/// Print out the control codes specified by this style to end up looking
|
||||
/// like the second string's styles.
|
||||
ExtraStyles(Style),
|
||||
|
||||
/// Converting between these two is impossible, so just send a reset
|
||||
/// command and then the second string's styles.
|
||||
Reset,
|
||||
|
||||
/// The before style is exactly the same as the after style, so no further
|
||||
/// control codes need to be printed.
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Difference {
|
||||
/// Compute the 'style difference' required to turn an existing style into
|
||||
/// the given, second style.
|
||||
///
|
||||
/// For example, to turn green text into green bold text, it's redundant
|
||||
/// to write a reset command then a second green+bold command, instead of
|
||||
/// just writing one bold command. This method should see that both styles
|
||||
/// use the foreground color green, and reduce it to a single command.
|
||||
///
|
||||
/// This method returns an enum value because it's not actually always
|
||||
/// possible to turn one style into another: for example, text could be
|
||||
/// made bold and underlined, but you can't remove the bold property
|
||||
/// without also removing the underline property. So when this has to
|
||||
/// happen, this function returns None, meaning that the entire set of
|
||||
/// styles should be reset and begun again.
|
||||
pub fn between(first: &Style, next: &Style) -> Difference {
|
||||
use self::Difference::*;
|
||||
|
||||
// XXX(Havvy): This algorithm is kind of hard to replicate without
|
||||
// having the Plain/Foreground enum variants, so I'm just leaving
|
||||
// it commented out for now, and defaulting to Reset.
|
||||
|
||||
if first == next {
|
||||
return Empty;
|
||||
}
|
||||
|
||||
// Cannot un-bold, so must Reset.
|
||||
if first.is_bold && !next.is_bold {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_dimmed && !next.is_dimmed {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_italic && !next.is_italic {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot un-underline, so must Reset.
|
||||
if first.is_underline && !next.is_underline {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_blink && !next.is_blink {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_reverse && !next.is_reverse {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_hidden && !next.is_hidden {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_strikethrough && !next.is_strikethrough {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from foreground to no foreground, so must Reset.
|
||||
if first.foreground.is_some() && next.foreground.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from background to no background, so must Reset.
|
||||
if first.background.is_some() && next.background.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
let mut extra_styles = Style::default();
|
||||
|
||||
if first.is_bold != next.is_bold {
|
||||
extra_styles.is_bold = true;
|
||||
}
|
||||
|
||||
if first.is_dimmed != next.is_dimmed {
|
||||
extra_styles.is_dimmed = true;
|
||||
}
|
||||
|
||||
if first.is_italic != next.is_italic {
|
||||
extra_styles.is_italic = true;
|
||||
}
|
||||
|
||||
if first.is_underline != next.is_underline {
|
||||
extra_styles.is_underline = true;
|
||||
}
|
||||
|
||||
if first.is_blink != next.is_blink {
|
||||
extra_styles.is_blink = true;
|
||||
}
|
||||
|
||||
if first.is_reverse != next.is_reverse {
|
||||
extra_styles.is_reverse = true;
|
||||
}
|
||||
|
||||
if first.is_hidden != next.is_hidden {
|
||||
extra_styles.is_hidden = true;
|
||||
}
|
||||
|
||||
if first.is_strikethrough != next.is_strikethrough {
|
||||
extra_styles.is_strikethrough = true;
|
||||
}
|
||||
|
||||
if first.foreground != next.foreground {
|
||||
extra_styles.foreground = next.foreground;
|
||||
}
|
||||
|
||||
if first.background != next.background {
|
||||
extra_styles.background = next.background;
|
||||
}
|
||||
|
||||
ExtraStyles(extra_styles)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Difference::*;
|
||||
use super::*;
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $first: expr; $next: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, Difference::between(&$first, &$next));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(nothing: Green.normal(); Green.normal() => Empty);
|
||||
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
|
||||
test!(lowercase: Green.bold(); Green.normal() => Reset);
|
||||
test!(nothing2: Green.bold(); Green.bold() => Empty);
|
||||
|
||||
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
|
||||
|
||||
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
|
||||
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
|
||||
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
|
||||
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
|
||||
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
|
||||
|
||||
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
|
||||
test!(removal_of_reverse: style().reverse(); style() => Reset);
|
||||
test!(removal_of_hidden: style().hidden(); style() => Reset);
|
||||
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
|
||||
test!(removal_of_blink: style().blink(); style() => Reset);
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
use crate::ansi::RESET;
|
||||
use crate::difference::Difference;
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// An `ANSIGenericString` includes a generic string type and a `Style` to
|
||||
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
|
||||
/// this type on `str` and `\[u8]`, respectively.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
style: Style,
|
||||
string: Cow<'a, S>,
|
||||
}
|
||||
|
||||
/// Cloning an `ANSIGenericString` will clone its underlying string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// let clone_string = plain_string.clone();
|
||||
/// assert_eq!(clone_string, plain_string);
|
||||
/// ```
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn clone(&self) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
style: self.style,
|
||||
string: self.string.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You might think that the hand-written Clone impl above is the same as the
|
||||
// one that gets generated with #[derive]. But it’s not *quite* the same!
|
||||
//
|
||||
// `str` is not Clone, and the derived Clone implementation puts a Clone
|
||||
// constraint on the S type parameter (generated using --pretty=expanded):
|
||||
//
|
||||
// ↓_________________↓
|
||||
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
|
||||
// for ANSIGenericString<'a, S> where
|
||||
// <S as ToOwned>::Owned: fmt::Debug { ... }
|
||||
//
|
||||
// This resulted in compile errors when you tried to derive Clone on a type
|
||||
// that used it:
|
||||
//
|
||||
// #[derive(PartialEq, Debug, Clone, Default)]
|
||||
// pub struct TextCellContents(Vec<ANSIString<'static>>);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
|
||||
//
|
||||
// The hand-written impl above can ignore that constraint and still compile.
|
||||
|
||||
/// An ANSI String is a string coupled with the `Style` to display it
|
||||
/// in a terminal.
|
||||
///
|
||||
/// Although not technically a string itself, it can be turned into
|
||||
/// one with the `to_string` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
/// use nu_ansi_term::Color::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// assert_eq!(&*plain_string, "a plain string");
|
||||
/// ```
|
||||
pub type AnsiString<'a> = AnsiGenericString<'a, str>;
|
||||
|
||||
/// An `AnsiByteString` represents a formatted series of bytes. Use
|
||||
/// `AnsiByteString` when styling text with an unknown encoding.
|
||||
pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>;
|
||||
|
||||
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn from(input: I) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
/// Directly access the style
|
||||
pub fn style_ref(&self) -> &Style {
|
||||
&self.style
|
||||
}
|
||||
|
||||
/// Directly access the style mutably
|
||||
pub fn style_ref_mut(&mut self) -> &mut Style {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.string.deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of `AnsiGenericStrings`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>])
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
S: PartialEq;
|
||||
|
||||
/// A set of `AnsiString`s collected together, in order to be written with a
|
||||
/// minimum of control characters.
|
||||
pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>;
|
||||
|
||||
/// A function to construct an `AnsiStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
/// A set of `AnsiByteString`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>;
|
||||
|
||||
/// A function to construct an `ANSIByteStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
// ---- paint functions ----
|
||||
|
||||
impl Style {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for individual ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let w: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteString<'a> {
|
||||
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
|
||||
/// sequences for the associated `Style` around the bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
write!(w, "{}", self.style.prefix())?;
|
||||
w.write_str(self.string.as_ref())?;
|
||||
write!(w, "{}", self.style.suffix())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for combined ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiStrings<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteStrings<'a> {
|
||||
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
|
||||
/// escape sequences for the associated `Style`s around each set of
|
||||
/// bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
use self::Difference::*;
|
||||
|
||||
let first = match self.0.first() {
|
||||
None => return Ok(()),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
write!(w, "{}", first.style.prefix())?;
|
||||
w.write_str(first.string.as_ref())?;
|
||||
|
||||
for window in self.0.windows(2) {
|
||||
match Difference::between(&window[0].style, &window[1].style) {
|
||||
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
|
||||
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
|
||||
Empty => { /* Do nothing! */ }
|
||||
}
|
||||
|
||||
w.write_str(&window[1].string)?;
|
||||
}
|
||||
|
||||
// Write the final reset string after all of the ANSIStrings have been
|
||||
// written, *except* if the last one has no styles, because it would
|
||||
// have already been written by this point.
|
||||
if let Some(last) = self.0.last() {
|
||||
if !last.style.is_plain() {
|
||||
write!(w, "{}", RESET)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tests ----
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub use super::super::AnsiStrings;
|
||||
pub use crate::style::Color::*;
|
||||
pub use crate::style::Style;
|
||||
|
||||
#[test]
|
||||
fn no_control_codes_for_plain() {
|
||||
let one = Style::default().paint("one");
|
||||
let two = Style::default().paint("two");
|
||||
let output = AnsiStrings(&[one, two]).to_string();
|
||||
assert_eq!(output, "onetwo");
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
use crate::{rgb::Rgb, Color};
|
||||
|
||||
/// Linear color gradient between two color stops
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Gradient {
|
||||
/// Start Color of Gradient
|
||||
pub start: Rgb,
|
||||
|
||||
/// End Color of Gradient
|
||||
pub end: Rgb,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
|
||||
#[inline]
|
||||
pub const fn new(start: Rgb, end: Rgb) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
|
||||
let start_grad = match start {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
let end_grad = match end {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
|
||||
Self {
|
||||
start: start_grad,
|
||||
end: end_grad,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the [Rgb] color between `start` and `end` for `t`
|
||||
pub fn at(&self, t: f32) -> Rgb {
|
||||
self.start.lerp(self.end, t)
|
||||
}
|
||||
|
||||
/// Returns the reverse of `self`
|
||||
#[inline]
|
||||
pub const fn reverse(&self) -> Self {
|
||||
Self::new(self.end, self.start)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build(&self, text: &str, target: TargetGround) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let temp = format!(
|
||||
"\x1B[{}m{}",
|
||||
self.at(i as f32 * delta).ansi_color_code(target),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let step = i as f32 * delta;
|
||||
let temp = format!(
|
||||
"\x1B[{};{}m{}",
|
||||
foreground
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Foreground),
|
||||
background
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Background),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetGround {
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
impl TargetGround {
|
||||
#[inline]
|
||||
pub const fn code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Foreground => 30,
|
||||
Self::Background => 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ANSIColorCode {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String;
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
//! This is a library for controlling colors and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are three main types in this crate that you need to be
|
||||
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
|
||||
//!
|
||||
//! A `Style` holds stylistic information: foreground and background colors,
|
||||
//! whether the text should be bold, or blinking, or other properties. The
|
||||
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
|
||||
//! string paired with a [`Style`].
|
||||
//!
|
||||
//! [`Color`] is also available as an alias to `Color`.
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Color`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//! ```
|
||||
//!
|
||||
//! It’s important to note that the `paint` method does *not* actually return a
|
||||
//! string with the ANSI control characters surrounding it. Instead, it returns
|
||||
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
|
||||
//! formatted, returns the characters. This allows strings to be printed with a
|
||||
//! minimum of [`String`] allocations being performed behind the scenes.
|
||||
//!
|
||||
//! If you *do* want to get at the escape codes, then you can convert the
|
||||
//! [`ANSIString`] to a string as you would any other `Display` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground color changes, you need to
|
||||
//! construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
//! You can do this by chaining methods based on a new `Style`, created with
|
||||
//! [`Style::new()`]. Each method creates a new style that has that specific
|
||||
//! property set. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//!
|
||||
//! println!("How about some {} and {}?",
|
||||
//! Style::new().bold().paint("bold"),
|
||||
//! Style::new().underline().paint("underline"));
|
||||
//! ```
|
||||
//!
|
||||
//! For brevity, these methods have also been implemented for `Color` values,
|
||||
//! so you can give your styles a foreground color without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::{Blue, Yellow};
|
||||
//!
|
||||
//! println!("Demonstrating {} and {}!",
|
||||
//! Blue.bold().paint("blue bold"),
|
||||
//! Yellow.underline().paint("yellow underline"));
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
|
||||
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
|
||||
//! background colors.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Color`.
|
||||
//! You can do this using the [`fg`] method:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! You can turn a `Color` into a `Style` with the [`normal`] method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Color` directly, but it’s useful in certain cases:
|
||||
//! for example, you may have a method that returns `Styles`, and need to
|
||||
//! represent both the “red bold” and “red, but not bold” styles with values of
|
||||
//! the same type. The `Style` struct also has a [`Default`] implementation if you
|
||||
//! want to have a style with *nothing* set.
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colors
|
||||
//!
|
||||
//! You can access the extended range of 256 colors by using the `Color::Fixed`
|
||||
//! variant, which takes an argument of the color number to use. This can be
|
||||
//! included wherever you would use a `Color`:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Fixed;
|
||||
//!
|
||||
//! Fixed(134).paint("A sort of light purple");
|
||||
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
//! ```
|
||||
//!
|
||||
//! The first sixteen of these values are the same as the normal and bold
|
||||
//! standard color variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `Color::Rgb` variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Rgb;
|
||||
//!
|
||||
//! Rgb(70, 130, 180).paint("Steel blue");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Combining successive colored strings
|
||||
//!
|
||||
//! The benefit of writing ANSI escape codes to the terminal is that they
|
||||
//! *stack*: you do not need to end every colored string with a reset code if
|
||||
//! the text that follows it is of a similar style. For example, if you want to
|
||||
//! have some blue text followed by some blue bold text, it’s possible to send
|
||||
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
|
||||
//! with a reset code without having to have an extra one between the two
|
||||
//! strings.
|
||||
//!
|
||||
//! This crate can optimise the ANSI codes that get printed in situations like
|
||||
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
|
||||
//! type takes a slice of several [`ANSIString`] values, and will iterate over
|
||||
//! each of them, printing only the codes for the styles that need to be updated
|
||||
//! as part of its formatting routine.
|
||||
//!
|
||||
//! The following code snippet uses this to enclose a binary number displayed in
|
||||
//! red bold text inside some red, but not bold, brackets:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//! use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
//!
|
||||
//! let some_value = format!("{:b}", 42);
|
||||
//! let strings: &[ANSIString<'static>] = &[
|
||||
//! Red.paint("["),
|
||||
//! Red.bold().paint(some_value),
|
||||
//! Red.paint("]"),
|
||||
//! ];
|
||||
//!
|
||||
//! println!("Value: {}", ANSIStrings(strings));
|
||||
//! ```
|
||||
//!
|
||||
//! There are several things to note here. Firstly, the [`paint`] method can take
|
||||
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
|
||||
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
|
||||
//! borrowed strings at the same time. This is used here to display a `String`,
|
||||
//! the result of the `format!` call, using the same mechanism as some
|
||||
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
|
||||
//! works in the same way as its singular counterpart, with a [`Display`]
|
||||
//! implementation that only performs the formatting when required.
|
||||
//!
|
||||
//! ## Byte strings
|
||||
//!
|
||||
//! This library also supports formatting `\[u8]` byte strings; this supports
|
||||
//! applications working with text in an unknown encoding. [`Style`] and
|
||||
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
|
||||
//! This type does not implement [`Display`], as it may not contain UTF-8, but
|
||||
//! it does provide a method [`write_to`] to write the result to any value that
|
||||
//! implements [`Write`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//!
|
||||
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
|
||||
//! [`ANSIByteString`] values with minimal escape sequences:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//! use nu_ansi_term::ANSIByteStrings;
|
||||
//!
|
||||
//! ANSIByteStrings(&[
|
||||
//! Green.paint("user data 1\n".as_bytes()),
|
||||
//! Green.bold().paint("user data 2\n".as_bytes()),
|
||||
//! ]).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
|
||||
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
|
||||
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
|
||||
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
//! [`Style`]: struct.Style.html
|
||||
//! [`Style::new()`]: struct.Style.html#method.new
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`ANSIString`]: type.ANSIString.html
|
||||
//! [`ANSIStrings`]: type.ANSIStrings.html
|
||||
//! [`ANSIByteString`]: type.ANSIByteString.html
|
||||
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
|
||||
//! [`write_to`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`paint`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`normal`]: enum.Color.html#method.normal
|
||||
//!
|
||||
//! [`bold`]: struct.Style.html#method.bold
|
||||
//! [`dimmed`]: struct.Style.html#method.dimmed
|
||||
//! [`italic`]: struct.Style.html#method.italic
|
||||
//! [`underline`]: struct.Style.html#method.underline
|
||||
//! [`blink`]: struct.Style.html#method.blink
|
||||
//! [`reverse`]: struct.Style.html#method.reverse
|
||||
//! [`hidden`]: struct.Style.html#method.hidden
|
||||
//! [`strikethrough`]: struct.Style.html#method.strikethrough
|
||||
//! [`fg`]: struct.Style.html#method.fg
|
||||
//! [`on`]: struct.Style.html#method.on
|
||||
|
||||
#![crate_name = "nu_ansi_term"]
|
||||
#![crate_type = "rlib"]
|
||||
#![crate_type = "dylib"]
|
||||
#![warn(missing_copy_implementations)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate doc_comment;
|
||||
|
||||
#[cfg(test)]
|
||||
doctest!("../README.md");
|
||||
|
||||
pub mod ansi;
|
||||
pub use ansi::{Infix, Prefix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Color, Style};
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod util;
|
||||
pub use util::*;
|
||||
|
||||
mod debug;
|
||||
|
||||
pub mod gradient;
|
||||
pub use gradient::*;
|
||||
|
||||
mod rgb;
|
||||
pub use rgb::*;
|
@ -1,173 +0,0 @@
|
||||
// Code liberally borrowed from here
|
||||
// https://github.com/navierr/coloriz
|
||||
use std::ops;
|
||||
use std::u32;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u8,
|
||||
/// Green
|
||||
pub g: u8,
|
||||
/// Blue
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Rgb {
|
||||
/// Creates a new [Rgb] color
|
||||
#[inline]
|
||||
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with a hex code
|
||||
#[inline]
|
||||
pub const fn from_hex(hex: u32) -> Self {
|
||||
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
|
||||
}
|
||||
|
||||
pub fn from_hex_string(hex: String) -> Self {
|
||||
if hex.chars().count() == 8 && hex.starts_with("0x") {
|
||||
// eprintln!("hex:{:?}", hex);
|
||||
let (_, value_string) = hex.split_at(2);
|
||||
// eprintln!("value_string:{:?}", value_string);
|
||||
let int_val = u64::from_str_radix(value_string, 16);
|
||||
match int_val {
|
||||
Ok(num) => Self::new(
|
||||
((num & 0xff0000) >> 16) as u8,
|
||||
((num & 0xff00) >> 8) as u8,
|
||||
(num & 0xff) as u8,
|
||||
),
|
||||
// Don't fail, just make the color black
|
||||
// Should we fail?
|
||||
_ => Self::new(0, 0, 0),
|
||||
}
|
||||
} else {
|
||||
// Don't fail, just make the color black.
|
||||
// Should we fail?
|
||||
Self::new(0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with three [f32] values
|
||||
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
|
||||
Self::new(
|
||||
(r.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(g.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(b.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color
|
||||
#[inline]
|
||||
pub const fn gray(x: u8) -> Self {
|
||||
Self::new(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color with a [f32] value
|
||||
pub fn gray_f32(x: f32) -> Self {
|
||||
Self::from_f32(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color from a [HSL] color
|
||||
// pub fn from_hsl(hsl: HSL) -> Self {
|
||||
// if hsl.s == 0.0 {
|
||||
// return Self::gray_f32(hsl.l);
|
||||
// }
|
||||
|
||||
// let q = if hsl.l < 0.5 {
|
||||
// hsl.l * (1.0 + hsl.s)
|
||||
// } else {
|
||||
// hsl.l + hsl.s - hsl.l * hsl.s
|
||||
// };
|
||||
// let p = 2.0 * hsl.l - q;
|
||||
// let h2c = |t: f32| {
|
||||
// let t = t.clamp(0.0, 1.0);
|
||||
// if 6.0 * t < 1.0 {
|
||||
// p + 6.0 * (q - p) * t
|
||||
// } else if t < 0.5 {
|
||||
// q
|
||||
// } else if 1.0 < 1.5 * t {
|
||||
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
|
||||
// } else {
|
||||
// p
|
||||
// }
|
||||
// };
|
||||
|
||||
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
|
||||
// }
|
||||
|
||||
/// Computes the linear interpolation between `self` and `other` for `t`
|
||||
pub fn lerp(&self, other: Self, t: f32) -> Self {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
self * (1.0 - t) + other * t
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb {
|
||||
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||
Self::new(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32)> for Rgb {
|
||||
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||
Self::from_f32(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ANSIColorCode;
|
||||
use crate::TargetGround;
|
||||
impl ANSIColorCode for Rgb {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String {
|
||||
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_add(rhs.r),
|
||||
lhs.g.saturating_add(rhs.g),
|
||||
lhs.b.saturating_add(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_sub(rhs.r),
|
||||
lhs.g.saturating_sub(rhs.g),
|
||||
lhs.b.saturating_sub(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
|
||||
Rgb::new(
|
||||
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
-(rgb: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
255 - rgb.r,
|
||||
255 - rgb.g,
|
||||
255 - rgb.b)
|
||||
}
|
||||
);
|
@ -1,626 +0,0 @@
|
||||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().bold().on(Color::Black);
|
||||
/// println!("{}", style.paint("Bold on black"));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub struct Style {
|
||||
/// The style's foreground color, if it has one.
|
||||
pub foreground: Option<Color>,
|
||||
|
||||
/// The style's background color, if it has one.
|
||||
pub background: Option<Color>,
|
||||
|
||||
/// Whether this style is bold.
|
||||
pub is_bold: bool,
|
||||
|
||||
/// Whether this style is dimmed.
|
||||
pub is_dimmed: bool,
|
||||
|
||||
/// Whether this style is italic.
|
||||
pub is_italic: bool,
|
||||
|
||||
/// Whether this style is underlined.
|
||||
pub is_underline: bool,
|
||||
|
||||
/// Whether this style is blinking.
|
||||
pub is_blink: bool,
|
||||
|
||||
/// Whether this style has reverse colors.
|
||||
pub is_reverse: bool,
|
||||
|
||||
/// Whether this style is hidden.
|
||||
pub is_hidden: bool,
|
||||
|
||||
/// Whether this style is struckthrough.
|
||||
pub is_strikethrough: bool,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Creates a new Style with no properties set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(&self) -> Style {
|
||||
Style {
|
||||
is_bold: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style {
|
||||
is_dimmed: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(&self) -> Style {
|
||||
Style {
|
||||
is_italic: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(&self) -> Style {
|
||||
Style {
|
||||
is_underline: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(&self) -> Style {
|
||||
Style {
|
||||
is_blink: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style {
|
||||
is_reverse: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style {
|
||||
is_hidden: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style {
|
||||
is_strikethrough: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().fg(Color::Yellow);
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn fg(&self, foreground: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(foreground),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().on(Color::Blue);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(&self, background: Color) -> Style {
|
||||
Style {
|
||||
background: Some(background),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// assert_eq!(true, Style::default().is_plain());
|
||||
/// assert_eq!(false, Style::default().bold().is_plain());
|
||||
/// ```
|
||||
pub fn is_plain(self) -> bool {
|
||||
self == Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
/// Returns a style with *no* properties set. Formatting text using this
|
||||
/// style returns the exact same text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
/// assert_eq!(None, Style::default().foreground);
|
||||
/// assert_eq!(None, Style::default().background);
|
||||
/// assert_eq!(false, Style::default().is_bold);
|
||||
/// assert_eq!("txt", Style::default().paint("txt").to_string());
|
||||
/// ```
|
||||
fn default() -> Style {
|
||||
Style {
|
||||
foreground: None,
|
||||
background: None,
|
||||
is_bold: false,
|
||||
is_dimmed: false,
|
||||
is_italic: false,
|
||||
is_underline: false,
|
||||
is_blink: false,
|
||||
is_reverse: false,
|
||||
is_hidden: false,
|
||||
is_strikethrough: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- colors ----
|
||||
|
||||
/// A color is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background color.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub enum Color {
|
||||
/// Color #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background color, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Color #0 (foreground code `90`, background code `100`).
|
||||
DarkGray,
|
||||
|
||||
/// Color #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Color #1 (foreground code `91`, background code `101`).
|
||||
LightRed,
|
||||
|
||||
/// Color #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Color #2 (foreground code `92`, background code `102`).
|
||||
LightGreen,
|
||||
|
||||
/// Color #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Color #3 (foreground code `93`, background code `103`).
|
||||
LightYellow,
|
||||
|
||||
/// Color #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Color #4 (foreground code `94`, background code `104`).
|
||||
LightBlue,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightPurple,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Magenta,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightMagenta,
|
||||
|
||||
/// Color #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Color #6 (foreground code `96`, background code `106`).
|
||||
LightCyan,
|
||||
|
||||
/// Color #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground color, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// Color #7 (foreground code `97`, background code `107`).
|
||||
LightGray,
|
||||
|
||||
/// A color number from 0 to 255, for use in 256-color terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colors can usually be changed in the terminal emulator.
|
||||
/// - colors 8 to 15 are brighter versions of the eight colors above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colors and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - colors 16 to 231 contain several palettes of bright colors,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - colors 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [color chart][cc].
|
||||
///
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit Rgb color, as specified by ISO-8613-3.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::White
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Returns a `Style` with the foreground color set to this color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Red.normal();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn normal(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Green.bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_bold: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Yellow.dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_dimmed: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Blue.italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_italic: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Purple.underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_underline: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// blink property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Cyan.blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_blink: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Black.reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_reverse: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::White.hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_hidden: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Fixed(244).strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_strikethrough: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// background color property set to the given color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Rgb(31, 31, 31).on(Color::White);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(self, background: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
background: Some(background),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
/// You can turn a `Color` into a `Style` with the foreground color set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
/// let green_foreground = Style::default().fg(Color::Green);
|
||||
/// assert_eq!(green_foreground, Color::Green.normal());
|
||||
/// assert_eq!(green_foreground, Color::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Color::Green));
|
||||
/// ```
|
||||
fn from(color: Color) -> Style {
|
||||
color.normal()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "derive_serde_style")]
|
||||
mod serde_json_tests {
|
||||
use super::{Color, Style};
|
||||
|
||||
#[test]
|
||||
fn color_serialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&colors).unwrap(),
|
||||
String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_deserialization() {
|
||||
let colors = [
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
for color in colors {
|
||||
let serialized = serde_json::to_string(&color).unwrap();
|
||||
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!(color, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_serialization() {
|
||||
let style = Style::default();
|
||||
|
||||
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
use crate::display::{AnsiString, AnsiStrings};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
|
||||
pub fn sub_string<'a>(
|
||||
start: usize,
|
||||
len: usize,
|
||||
strs: &AnsiStrings<'a>,
|
||||
) -> Vec<AnsiString<'static>> {
|
||||
let mut vec = Vec::new();
|
||||
let mut pos = start;
|
||||
let mut len_rem = len;
|
||||
|
||||
for i in strs.0.iter() {
|
||||
let fragment = i.deref();
|
||||
let frag_len = fragment.len();
|
||||
if pos >= frag_len {
|
||||
pos -= frag_len;
|
||||
continue;
|
||||
}
|
||||
if len_rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let end = pos + len_rem;
|
||||
let pos_end = if end >= frag_len { frag_len } else { end };
|
||||
|
||||
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
|
||||
|
||||
if end <= frag_len {
|
||||
break;
|
||||
}
|
||||
|
||||
len_rem -= pos_end - pos;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
|
||||
pub fn unstyle(strs: &AnsiStrings) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
for i in strs.0.iter() {
|
||||
s += i.deref();
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
|
||||
pub fn unstyled_len(strs: &AnsiStrings) -> usize {
|
||||
let mut l = 0;
|
||||
for i in strs.0.iter() {
|
||||
l += i.deref().len();
|
||||
}
|
||||
l
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Color::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let l = [
|
||||
Black.paint("first"),
|
||||
Red.paint("-second"),
|
||||
White.paint("-third"),
|
||||
];
|
||||
let a = AnsiStrings(&l);
|
||||
assert_eq!(unstyle(&a), "first-second-third");
|
||||
assert_eq!(unstyled_len(&a), 18);
|
||||
|
||||
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
|
||||
assert_eq!(sub_string(3, 11, &a), l2);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/// Enables ANSI code support on Windows 10.
|
||||
///
|
||||
/// This uses Windows API calls to alter the properties of the console that
|
||||
/// the program is running in.
|
||||
///
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
|
||||
///
|
||||
/// Returns a `Result` with the Windows error code if unsuccessful.
|
||||
#[cfg(windows)]
|
||||
pub fn enable_ansi_support() -> Result<(), u32> {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::null_mut;
|
||||
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
|
||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
|
||||
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
unsafe {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
|
||||
let console_out_name: Vec<u16> =
|
||||
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
|
||||
let console_handle = CreateFileW(
|
||||
console_out_name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_WRITE,
|
||||
null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
if console_handle == INVALID_HANDLE_VALUE {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut console_mode: u32 = 0;
|
||||
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// VT processing not already enabled?
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
if 0 == SetConsoleMode(
|
||||
console_handle,
|
||||
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
pub trait AnyWrite {
|
||||
type Wstr: ?Sized;
|
||||
type Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn fmt::Write + 'a {
|
||||
type Wstr = str;
|
||||
type Error = fmt::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_str(self, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn io::Write + 'a {
|
||||
type Wstr = [u8];
|
||||
type Error = io::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
io::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
io::Write::write_all(self, s)
|
||||
}
|
||||
}
|
@ -1,43 +1,26 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.42.0"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
version = "0.60.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nu-completion = { version = "0.42.0", path="../nu-completion" }
|
||||
nu-command = { version = "0.42.0", path="../nu-command" }
|
||||
nu-data = { version = "0.42.0", path="../nu-data" }
|
||||
nu-engine = { version = "0.42.0", path="../nu-engine" }
|
||||
nu-errors = { version = "0.42.0", path="../nu-errors" }
|
||||
nu-parser = { version = "0.42.0", path="../nu-parser" }
|
||||
nu-protocol = { version = "0.42.0", path="../nu-protocol" }
|
||||
nu-source = { version = "0.42.0", path="../nu-source" }
|
||||
nu-stream = { version = "0.42.0", path="../nu-stream" }
|
||||
nu-ansi-term = { version = "0.42.0", path="../nu-ansi-term" }
|
||||
nu-path = { version = "0.42.0", path="../nu-path" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.60.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.60.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.60.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.60.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.60.0" }
|
||||
nu-ansi-term = "0.45.0"
|
||||
|
||||
indexmap ="1.6.1"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
rustyline = { version="9.0.0", optional=true }
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
shadow-rs = { version = "0.8.1", default-features = false, optional = true }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_yaml = "0.8.16"
|
||||
lazy_static = "1.4.0"
|
||||
nu-color-config = { path = "../nu-color-config" }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.8.1"
|
||||
crossterm = "0.23.0"
|
||||
crossterm_winapi = "0.9.0"
|
||||
miette = { version = "4.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
reedline = "0.3.0"
|
||||
|
||||
log = "0.4"
|
||||
is_executable = "1.0.1"
|
||||
|
||||
[features]
|
||||
default = ["shadow-rs"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
plugin = []
|
||||
|
@ -1,4 +0,0 @@
|
||||
# nu-cli
|
||||
|
||||
This crate provides the fundamental needs when creating the Nushell interactive REPL. In it, you'll find features for interacting with the line editor (the piece which writes the prompt and takes input from the user), keybindings, handlers for the commandline arguments passed to the REPL as it starts up, and more.
|
||||
|
@ -1,3 +0,0 @@
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
@ -1,638 +0,0 @@
|
||||
mod logger;
|
||||
mod options;
|
||||
mod options_parser;
|
||||
pub mod stopwatch;
|
||||
|
||||
use self::stopwatch::Stopwatch;
|
||||
use lazy_static::lazy_static;
|
||||
use nu_command::{commands::NuSignature as Nu, utils::test_bins as binaries};
|
||||
use nu_engine::{get_full_help, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Call, Expression, SpannedExpression, Synthetic};
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::{Span, Tag};
|
||||
use nu_stream::InputStream;
|
||||
pub use options::{CliOptions, NuScript, Options};
|
||||
use options_parser::{NuParser, OptionsParser};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref STOPWATCH: Mutex<Stopwatch> = {
|
||||
let mut sw = Stopwatch::default();
|
||||
sw.start();
|
||||
sw.stop();
|
||||
Mutex::new(sw)
|
||||
};
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
parser: Box<dyn OptionsParser>,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
|
||||
Self { parser, options }
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), ShellError> {
|
||||
let nu = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
let ui = App::new(nu, options);
|
||||
|
||||
ui.main(args)
|
||||
}
|
||||
|
||||
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
|
||||
if self.perf() {
|
||||
// start the stopwatch running
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
let argv = quote_positionals(argv).join(" ");
|
||||
|
||||
if let Err(cause) = self.parse(&argv) {
|
||||
self.parser
|
||||
.context()
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(cause, &nu_source::Text::from(argv));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if self.help() {
|
||||
let context = self.parser.context();
|
||||
let stream = nu_stream::OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Nu, &context.scope))
|
||||
.into_value(nu_source::Tag::unknown()),
|
||||
);
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
if self.perf() {
|
||||
// stop the stopwatch since we're exiting
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
eprintln!(
|
||||
"help {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if self.version() {
|
||||
let context = self.parser.context();
|
||||
|
||||
let stream = nu_command::commands::version(nu_engine::CommandArgs {
|
||||
context: context.clone(),
|
||||
call_info: nu_engine::UnevaluatedCallInfo {
|
||||
args: Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("version".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
name_tag: Tag::unknown(),
|
||||
},
|
||||
input: InputStream::empty(),
|
||||
})?;
|
||||
|
||||
let stream = {
|
||||
let command = context
|
||||
.get_command("pivot")
|
||||
.expect("could not find version command");
|
||||
|
||||
context.run_command(
|
||||
command,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("pivot".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?
|
||||
};
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
if self.perf() {
|
||||
// stop the stopwatch since we're exiting
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
eprintln!(
|
||||
"version {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(bin) = self.testbin() {
|
||||
match bin.as_deref() {
|
||||
Ok("echo_env") => binaries::echo_env(),
|
||||
Ok("cococo") => binaries::cococo(),
|
||||
Ok("meow") => binaries::meow(),
|
||||
Ok("iecho") => binaries::iecho(),
|
||||
Ok("fail") => binaries::fail(),
|
||||
Ok("nonu") => binaries::nonu(),
|
||||
Ok("chop") => binaries::chop(),
|
||||
Ok("repeater") => binaries::repeater(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut opts = CliOptions::new();
|
||||
opts.config = self.config().map(std::ffi::OsString::from);
|
||||
opts.stdin = self.takes_stdin();
|
||||
opts.save_history = self.save_history();
|
||||
opts.perf = self.perf();
|
||||
|
||||
use logger::{configure, debug_filters, logger, trace_filters};
|
||||
|
||||
logger(|builder| {
|
||||
configure(self, builder)?;
|
||||
trace_filters(self, builder)?;
|
||||
debug_filters(self, builder)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start()
|
||||
}
|
||||
|
||||
if let Some(commands) = self.commands() {
|
||||
let commands = commands?;
|
||||
let script = NuScript::code(&commands)?;
|
||||
opts.scripts = vec![script];
|
||||
let context = crate::create_default_context(false)?;
|
||||
return crate::run_script_file(context, opts);
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new spit
|
||||
eprintln!(
|
||||
"commands using -c at launch: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
if let Some(scripts) = self.scripts() {
|
||||
let mut source_files = vec![];
|
||||
for script in scripts {
|
||||
let script_name = script?;
|
||||
let path = std::ffi::OsString::from(&script_name);
|
||||
|
||||
match NuScript::source_file(path.as_os_str()) {
|
||||
Ok(file) => source_files.push(file),
|
||||
Err(_) => {
|
||||
eprintln!("File not found: {}", script_name);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for file in source_files {
|
||||
let mut opts = opts.clone();
|
||||
opts.scripts = vec![file];
|
||||
|
||||
let context = crate::create_default_context(false)?;
|
||||
crate::run_script_file(context, opts)?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
eprintln!(
|
||||
"script file(s) passed in: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
let context = crate::create_default_context(true)?;
|
||||
|
||||
if !self.skip_plugins() {
|
||||
let _ = crate::register_plugins(&context);
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
eprintln!(
|
||||
"plugins registered: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
{
|
||||
crate::cli(context, opts)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rustyline-support"))]
|
||||
{
|
||||
println!("Nushell needs the 'rustyline-support' feature for CLI support");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commands(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("commands").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn perf(&self) -> bool {
|
||||
self.options
|
||||
.get("perf")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn help(&self) -> bool {
|
||||
self.options
|
||||
.get("help")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> bool {
|
||||
self.options
|
||||
.get("version")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("args").map(|v| {
|
||||
v.table_entries()
|
||||
.map(|v| match &v.value {
|
||||
UntaggedValue::Error(err) => Err(err.clone()),
|
||||
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn takes_stdin(&self) -> bool {
|
||||
self.options
|
||||
.get("stdin")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Option<String> {
|
||||
self.options
|
||||
.get("config-file")
|
||||
.map(|v| v.as_string().expect("not a string"))
|
||||
}
|
||||
|
||||
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("develop").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("debug").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("loglevel").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("testbin").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn skip_plugins(&self) -> bool {
|
||||
self.options
|
||||
.get("skip-plugins")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn save_history(&self) -> bool {
|
||||
self.options
|
||||
.get("no-history")
|
||||
.map(|v| !matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
|
||||
self.parser.parse(args).map(|options| {
|
||||
self.options.swap(&options);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn quote_positionals(parameters: &[String]) -> Vec<String> {
|
||||
parameters
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|arg| {
|
||||
if arg.contains(' ') {
|
||||
format!("\"{}\"", arg)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn consume(context: &EvaluationContext, stream: InputStream) -> Result<(), ShellError> {
|
||||
let autoview_cmd = context
|
||||
.get_command("autoview")
|
||||
.expect("could not find autoview command");
|
||||
|
||||
let stream = context.run_command(
|
||||
autoview_cmd,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?;
|
||||
|
||||
for _ in stream {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cli_app() -> App {
|
||||
let parser = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
|
||||
App::new(parser, options)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu")?;
|
||||
assert!(!ui.version());
|
||||
assert!(!ui.help());
|
||||
assert!(!ui.takes_stdin());
|
||||
assert!(ui.save_history());
|
||||
assert!(!ui.skip_plugins());
|
||||
assert_eq!(ui.config(), None);
|
||||
assert_eq!(ui.loglevel(), None);
|
||||
assert_eq!(ui.debug(), None);
|
||||
assert_eq!(ui.develop(), None);
|
||||
assert_eq!(ui.testbin(), None);
|
||||
assert_eq!(ui.commands(), None);
|
||||
assert_eq!(ui.scripts(), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
|
||||
assert!(ui.config().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --develop=cli,parser")?;
|
||||
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --debug=cli,run")?;
|
||||
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_loglevels() -> Result<(), ShellError> {
|
||||
for level in ["error", "warn", "info", "debug", "trace"] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --loglevel={}", level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --loglevel=nada")?;
|
||||
assert_eq!(
|
||||
ui.loglevel().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error("nada is not supported."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_login() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu -l")?;
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --login")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu code.nu bootstrap.nu")?;
|
||||
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
|
||||
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_test_binaries() -> Result<(), ShellError> {
|
||||
for binarie_name in [
|
||||
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
|
||||
] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --testbin={}", binarie_name);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --testbin=andres")?;
|
||||
assert_eq!(
|
||||
ui.testbin().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"andres is not supported."
|
||||
))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_version() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --version")?;
|
||||
assert!(ui.version());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_help() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --help")?;
|
||||
assert!(ui.help());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_take_stdin() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --stdin")?;
|
||||
assert!(ui.takes_stdin());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --no-history")?;
|
||||
assert!(!ui.save_history());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --skip-plugins")?;
|
||||
assert!(ui.skip_plugins());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"ls | get name\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
|
||||
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"echo 'hola'\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knows_custom_configurations() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --config-file /path/to/config.toml")?;
|
||||
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
use super::App;
|
||||
use log::LevelFilter;
|
||||
use nu_errors::ShellError;
|
||||
use pretty_env_logger::env_logger::Builder;
|
||||
|
||||
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
|
||||
let mut builder = pretty_env_logger::formatted_builder();
|
||||
f(&mut builder)?;
|
||||
let _ = builder.try_init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(level) = app.loglevel() {
|
||||
let level = match level.as_deref() {
|
||||
Ok("error") => LevelFilter::Error,
|
||||
Ok("warn") => LevelFilter::Warn,
|
||||
Ok("info") => LevelFilter::Info,
|
||||
Ok("debug") => LevelFilter::Debug,
|
||||
Ok("trace") => LevelFilter::Trace,
|
||||
Ok(_) | Err(_) => LevelFilter::Warn,
|
||||
};
|
||||
|
||||
logger.filter_module("nu", level);
|
||||
};
|
||||
|
||||
if let Ok(s) = std::env::var("RUST_LOG") {
|
||||
logger.parse_filters(&s);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.develop() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Trace);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.debug() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Debug);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CliOptions {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
pub perf: bool,
|
||||
}
|
||||
|
||||
impl Default for CliOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
perf: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
inner: RefCell<IndexMap<String, Value>>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(IndexMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
self.inner.borrow().get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &str, value: Value) {
|
||||
self.inner.borrow_mut().insert(key.into(), value);
|
||||
}
|
||||
|
||||
pub fn shift(&self) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(ref mut args),
|
||||
..
|
||||
}) = self.inner.borrow_mut().get_mut("args")
|
||||
{
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap(&self, other: &Options) {
|
||||
self.inner.swap(&other.inner);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code(content: &str) -> Result<Self, ShellError> {
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: content.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
use super::Options;
|
||||
|
||||
use nu_command::commands::{loglevels, testbins, NuSignature as Nu};
|
||||
use nu_command::commands::{Autoview, Pivot, Table, Version as NuVersion};
|
||||
use nu_engine::{whole_stream_command, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
pub struct NuParser {
|
||||
context: EvaluationContext,
|
||||
}
|
||||
|
||||
pub trait OptionsParser {
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError>;
|
||||
fn context(&self) -> &EvaluationContext;
|
||||
}
|
||||
|
||||
impl NuParser {
|
||||
pub fn new() -> Self {
|
||||
let context = EvaluationContext::basic();
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(Nu {}),
|
||||
whole_stream_command(NuVersion {}),
|
||||
whole_stream_command(Autoview {}),
|
||||
whole_stream_command(Pivot {}),
|
||||
whole_stream_command(Table {}),
|
||||
]);
|
||||
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsParser for NuParser {
|
||||
fn context(&self) -> &EvaluationContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError> {
|
||||
let options = Options::default();
|
||||
let (lite_result, _err) = nu_parser::lex(input, 0, nu_parser::NewlineMode::Normal);
|
||||
let (lite_result, _err) = nu_parser::parse_block(lite_result);
|
||||
|
||||
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
|
||||
|
||||
if let Some(reason) = err {
|
||||
return Err(reason.into());
|
||||
}
|
||||
|
||||
match parsed.block[0].pipelines[0].list[0] {
|
||||
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
|
||||
if let Some(ref params) = args.named {
|
||||
params.iter().for_each(|(k, v)| {
|
||||
let value = match v {
|
||||
NamedValue::AbsentSwitch => {
|
||||
Some(UntaggedValue::from(false).into_untagged_value())
|
||||
}
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
|
||||
}
|
||||
NamedValue::AbsentValue => None,
|
||||
NamedValue::Value(span, exprs) => {
|
||||
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
|
||||
.expect("value");
|
||||
Some(value.value.into_value(Tag::from(span)))
|
||||
}
|
||||
};
|
||||
|
||||
let value = value.map(|v| match k.as_ref() {
|
||||
"testbin" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if testbins().iter().any(|n| name == *n) {
|
||||
v
|
||||
} else {
|
||||
UntaggedValue::Error(ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
))
|
||||
.into_value(v.tag)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
"loglevel" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if loglevels().iter().any(|n| name == *n) {
|
||||
v
|
||||
} else {
|
||||
UntaggedValue::Error(ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
))
|
||||
.into_value(v.tag)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
_ => v,
|
||||
});
|
||||
|
||||
if let Some(value) = value {
|
||||
options.put(k, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut positional_args = vec![];
|
||||
|
||||
if let Some(positional) = &args.positional {
|
||||
for pos in positional {
|
||||
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
|
||||
positional_args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if !positional_args.is_empty() {
|
||||
options.put(
|
||||
"args",
|
||||
UntaggedValue::Table(positional_args).into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Error(ref reason) => {
|
||||
return Err(reason.clone().into());
|
||||
}
|
||||
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use std::default::Default;
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stopwatch {
|
||||
/// The time the stopwatch was started last, if ever.
|
||||
start_time: Option<Instant>,
|
||||
/// The time the stopwatch was split last, if ever.
|
||||
split_time: Option<Instant>,
|
||||
/// The time elapsed while the stopwatch was running (between start() and stop()).
|
||||
elapsed: Duration,
|
||||
}
|
||||
|
||||
impl Default for Stopwatch {
|
||||
fn default() -> Stopwatch {
|
||||
Stopwatch {
|
||||
start_time: None,
|
||||
split_time: None,
|
||||
elapsed: Duration::from_secs(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stopwatch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
return write!(f, "{}ms", self.elapsed_ms());
|
||||
}
|
||||
}
|
||||
|
||||
impl Stopwatch {
|
||||
/// Returns a new stopwatch.
|
||||
pub fn new() -> Stopwatch {
|
||||
let sw: Stopwatch = Default::default();
|
||||
sw
|
||||
}
|
||||
|
||||
/// Returns a new stopwatch which will immediately be started.
|
||||
pub fn start_new() -> Stopwatch {
|
||||
let mut sw = Stopwatch::new();
|
||||
sw.start();
|
||||
sw
|
||||
}
|
||||
|
||||
/// Starts the stopwatch.
|
||||
pub fn start(&mut self) {
|
||||
self.start_time = Some(Instant::now());
|
||||
}
|
||||
|
||||
/// Stops the stopwatch.
|
||||
pub fn stop(&mut self) {
|
||||
self.elapsed = self.elapsed();
|
||||
self.start_time = None;
|
||||
self.split_time = None;
|
||||
}
|
||||
|
||||
/// Resets all counters and stops the stopwatch.
|
||||
pub fn reset(&mut self) {
|
||||
self.elapsed = Duration::from_secs(0);
|
||||
self.start_time = None;
|
||||
self.split_time = None;
|
||||
}
|
||||
|
||||
/// Resets and starts the stopwatch again.
|
||||
pub fn restart(&mut self) {
|
||||
self.reset();
|
||||
self.start();
|
||||
}
|
||||
|
||||
/// Returns whether the stopwatch is running.
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.start_time.is_some()
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the start of the stopwatch.
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
match self.start_time {
|
||||
// stopwatch is running
|
||||
Some(t1) => t1.elapsed() + self.elapsed,
|
||||
// stopwatch is not running
|
||||
None => self.elapsed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the start of the stopwatch in milliseconds.
|
||||
pub fn elapsed_ms(&self) -> i64 {
|
||||
let dur = self.elapsed();
|
||||
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since last split or start/restart.
|
||||
///
|
||||
/// If the stopwatch is in stopped state this will always return a zero Duration.
|
||||
pub fn elapsed_split(&mut self) -> Duration {
|
||||
match self.start_time {
|
||||
// stopwatch is running
|
||||
Some(start) => {
|
||||
let res = match self.split_time {
|
||||
Some(split) => split.elapsed(),
|
||||
None => start.elapsed(),
|
||||
};
|
||||
self.split_time = Some(Instant::now());
|
||||
res
|
||||
}
|
||||
// stopwatch is not running
|
||||
None => Duration::from_secs(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since last split or start/restart in milliseconds.
|
||||
///
|
||||
/// If the stopwatch is in stopped state this will always return zero.
|
||||
pub fn elapsed_split_ms(&mut self) -> i64 {
|
||||
let dur = self.elapsed_split();
|
||||
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
|
||||
}
|
||||
}
|
@ -1,538 +0,0 @@
|
||||
use crate::app::STOPWATCH;
|
||||
use crate::line_editor::configure_ctrl_c;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_engine::script::{process_script, LineResult};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::line_editor::{
|
||||
configure_rustyline_editor, convert_rustyline_result_to_string,
|
||||
default_rustyline_editor_configuration, nu_line_editor_helper,
|
||||
};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_data::config;
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_stream::InputStream;
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{self, error::ReadlineError};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_path::expand_tilde;
|
||||
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
|
||||
|
||||
use log::trace;
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Name of environment variable where the prompt could be stored
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
let mut search_paths = Vec::new();
|
||||
|
||||
// Automatically add path `nu` is in as a search path
|
||||
if let Ok(exe_path) = env::current_exe() {
|
||||
if let Some(exe_dir) = exe_path.parent() {
|
||||
search_paths.push(exe_dir.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
}) = config.get("plugin_dirs")
|
||||
{
|
||||
for pipeline in pipelines {
|
||||
if let Ok(plugin_dir) = pipeline.as_string() {
|
||||
search_paths.push(expand_tilde(plugin_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn run_script_file(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
|
||||
let _ = register_plugins(&context);
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
let script = options
|
||||
.scripts
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
||||
|
||||
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_prompt_string(cwd: &str) -> String {
|
||||
format!(
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix().to_string(),
|
||||
cwd,
|
||||
nu_ansi_term::ansi::RESET,
|
||||
Color::Cyan.bold().prefix().to_string(),
|
||||
current_branch(),
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn evaluate_prompt_string(prompt_line: &str, context: &EvaluationContext, cwd: &str) -> String {
|
||||
context.scope.enter_scope();
|
||||
let (prompt_block, err) = nu_parser::parse(prompt_line, 0, &context.scope);
|
||||
|
||||
if err.is_some() {
|
||||
context.scope.exit_scope();
|
||||
default_prompt_string(cwd)
|
||||
} else {
|
||||
let run_result = run_block(
|
||||
&prompt_block,
|
||||
context,
|
||||
InputStream::empty(),
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
context.scope.exit_scope();
|
||||
|
||||
match run_result {
|
||||
Ok(result) => match result.collect_string(Tag::unknown()) {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
maybe_print_errors(context, Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".into()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".into()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn cli(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
|
||||
let startup_commands_start_time = std::time::Instant::now();
|
||||
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION_MS",
|
||||
startup_commands_start_time
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"config loaded: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//Configure rustyline
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
let history_path = if let Some(cfg) = &context.configs().lock().global_config {
|
||||
let _ = configure_rustyline_editor(&mut rl, cfg);
|
||||
let helper = Some(nu_line_editor_helper(&context, cfg));
|
||||
rl.set_helper(helper);
|
||||
nu_data::config::path::history_path_or_default(cfg)
|
||||
} else {
|
||||
nu_data::config::path::default_history_path()
|
||||
};
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"rustyline configuration: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
// Don't load history if it's not necessary
|
||||
if options.save_history {
|
||||
let _ = rl.load_history(&history_path);
|
||||
}
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"history load: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//set vars from cfg if present
|
||||
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs().lock().global_config
|
||||
{
|
||||
(
|
||||
cfg.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false),
|
||||
cfg.var("prompt"),
|
||||
)
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"load custom prompt: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//Check whether dir we start in contains local cfg file and if so load it.
|
||||
load_local_cfg_if_present(&context);
|
||||
|
||||
// Give ourselves a scope to work in
|
||||
context.scope.enter_scope();
|
||||
|
||||
let mut session_text = String::new();
|
||||
let mut line_start: usize = 0;
|
||||
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
nu_command::commands::core_version()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = nu_ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"timing stopped. starting run loop: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
}
|
||||
|
||||
loop {
|
||||
if context.ctrl_c().load(Ordering::SeqCst) {
|
||||
context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cwd = context.shell_manager().path();
|
||||
|
||||
// Check if the PROMPT_COMMAND env variable is set. This env variable
|
||||
// contains nu code that is used to overwrite the prompt
|
||||
let colored_prompt = match context.scope.get_env(PROMPT_COMMAND) {
|
||||
Some(env_prompt) => evaluate_prompt_string(&env_prompt, &context, &cwd),
|
||||
None => {
|
||||
if let Some(prompt) = &prompt {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
evaluate_prompt_string(&prompt_line, &context, &cwd)
|
||||
} else {
|
||||
default_prompt_string(&cwd)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let prompt = {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(&colored_prompt) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
"> ".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(helper) = rl.helper_mut() {
|
||||
helper.colored_prompt = colored_prompt;
|
||||
}
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
readline = rl.readline_with_initial(&prompt, (cmd, ""));
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
if let Ok(line) = &readline {
|
||||
line_start = session_text.len();
|
||||
session_text.push_str(line);
|
||||
session_text.push('\n');
|
||||
}
|
||||
|
||||
// start time for command duration
|
||||
let cmd_start_time = std::time::Instant::now();
|
||||
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(_) => process_script(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
),
|
||||
x => x,
|
||||
};
|
||||
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION_MS",
|
||||
cmd_start_time.elapsed().as_millis().to_string(),
|
||||
);
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
if options.save_history {
|
||||
rl.clear_history();
|
||||
std::fs::remove_file(&history_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
context
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(err, &Text::from(session_text.clone()));
|
||||
|
||||
// I am not so sure, we don't need maybe_print_errors here (as we printed an err
|
||||
// above), because maybe_print_errors also clears the errors.
|
||||
// TODO Analyze where above err comes from, and whether we need to clear
|
||||
// context.errors here
|
||||
// Or just be consistent and return errors always in context.errors...
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = context
|
||||
.configs()
|
||||
.lock()
|
||||
.global_config
|
||||
.as_ref()
|
||||
.and_then(|cfg| cfg.var("ctrlc_exit"))
|
||||
.map(|ctrl_c| ctrl_c.is_true())
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
ctrlcbreak = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::CtrlD => {
|
||||
context.shell_manager().remove_at_current();
|
||||
if context.shell_manager().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctrlcbreak = false;
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn load_local_cfg_if_present(context: &EvaluationContext) {
|
||||
trace!("Loading local cfg if present");
|
||||
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager().path())) {
|
||||
Ok(Some(cfg_path)) => {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
|
||||
context.host().lock().print_err(err, &Text::from(""))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
//Report error while checking for local cfg file
|
||||
context.host().lock().print_err(e, &Text::from(""))
|
||||
}
|
||||
Ok(None) => {
|
||||
//No local cfg file present in start dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
|
||||
context.host().lock().print_err(err, &Text::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_global_cfg(context: &EvaluationContext) {
|
||||
match config::default_path() {
|
||||
Ok(path) => {
|
||||
load_cfg_as_global_cfg(context, path);
|
||||
}
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_plugins(context: &EvaluationContext) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
||||
// FIXME: do we still need this?
|
||||
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||
s
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
ctx.scope.enter_scope();
|
||||
let (classified_block, err) = nu_parser::parse(line, 0, &ctx.scope);
|
||||
if let Some(err) = err {
|
||||
ctx.scope.exit_scope();
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
|
||||
let result = run_block(
|
||||
&classified_block,
|
||||
ctx,
|
||||
input_stream,
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
result?.collect_string(Tag::unknown()).map(|x| x.item)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn current_branch() -> String {
|
||||
#[cfg(feature = "shadow-rs")]
|
||||
{
|
||||
Some(shadow_rs::branch())
|
||||
.map(|x| x.trim().to_string())
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(|x| format!("({})", x))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
#[cfg(not(feature = "shadow-rs"))]
|
||||
{
|
||||
"".to_string()
|
||||
}
|
||||
}
|
103
crates/nu-cli/src/commands.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::util::report_error;
|
||||
use log::info;
|
||||
use miette::Result;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{parse, trim_quotes};
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
Config, PipelineData, Spanned,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
init_cwd: &Path,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
// Run a command (or commands) given to us by the user
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (input, _) = if commands.item.starts_with('\'') || commands.item.starts_with('"') {
|
||||
(
|
||||
trim_quotes(commands.item.as_bytes()),
|
||||
commands.span.start + 1,
|
||||
)
|
||||
} else {
|
||||
(commands.item.as_bytes(), commands.span.start)
|
||||
};
|
||||
|
||||
let (output, err) = parse(&mut working_set, None, input, false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
let config = match stack.get_config() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &e);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Make a note of the exceptions we see for externals that look like math expressions
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
|
||||
// Merge the delta in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &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(())
|
||||
}
|
636
crates/nu-cli/src/completions.rs
Normal file
@ -0,0 +1,636 @@
|
||||
use nu_engine::eval_call;
|
||||
use nu_parser::{flatten_expression, parse, trim_quotes, FlatShape};
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, Span, Value, CONFIG_VARIABLE_ID,
|
||||
};
|
||||
use reedline::Completer;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub struct CompletionOptions {
|
||||
case_sensitive: bool,
|
||||
positional: bool,
|
||||
sort: bool,
|
||||
}
|
||||
|
||||
impl Default for CompletionOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
case_sensitive: true,
|
||||
positional: true,
|
||||
sort: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuCompleter {
|
||||
engine_state: EngineState,
|
||||
config: Option<Value>,
|
||||
}
|
||||
|
||||
impl NuCompleter {
|
||||
pub fn new(engine_state: EngineState, config: Option<Value>) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn external_command_completion(&self, prefix: &str) -> Vec<String> {
|
||||
let mut executables = vec![];
|
||||
|
||||
let paths = self.engine_state.env_vars.get("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 !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
) && matches!(
|
||||
item.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().starts_with(prefix)),
|
||||
Some(true)
|
||||
) && is_executable::is_executable(&item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executables
|
||||
}
|
||||
|
||||
fn complete_variables(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<(reedline::Span, String)> {
|
||||
let mut output = vec![];
|
||||
|
||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
||||
|
||||
for builtin in builtins {
|
||||
if builtin.as_bytes().starts_with(prefix) {
|
||||
output.push((
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
builtin.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for scope in &working_set.delta.scope {
|
||||
for v in &scope.vars {
|
||||
if v.0.starts_with(prefix) {
|
||||
output.push((
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(v.0).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
for scope in &self.engine_state.scope {
|
||||
for v in &scope.vars {
|
||||
if v.0.starts_with(prefix) {
|
||||
output.push((
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(v.0).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dedup();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn complete_commands(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
find_externals: bool,
|
||||
) -> Vec<(reedline::Span, String)> {
|
||||
let prefix = working_set.get_span_contents(span);
|
||||
|
||||
let results = working_set
|
||||
.find_commands_by_prefix(prefix)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
(
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(&x).to_string(),
|
||||
)
|
||||
});
|
||||
|
||||
let results_aliases =
|
||||
working_set
|
||||
.find_aliases_by_prefix(prefix)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
(
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(&x).to_string(),
|
||||
)
|
||||
});
|
||||
|
||||
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
||||
|
||||
let prefix = working_set.get_span_contents(span);
|
||||
let prefix = String::from_utf8_lossy(prefix).to_string();
|
||||
if find_externals {
|
||||
let results_external =
|
||||
self.external_command_completion(&prefix)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
(
|
||||
reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
x,
|
||||
)
|
||||
});
|
||||
|
||||
for external in results_external {
|
||||
if results.contains(&external) {
|
||||
results.push((external.0, format!("^{}", external.1)))
|
||||
} else {
|
||||
results.push(external)
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
} else {
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
let mut line = line.to_string();
|
||||
line.insert(pos, 'a');
|
||||
let pos = offset + pos;
|
||||
let (output, _err) = parse(
|
||||
&mut working_set,
|
||||
Some("completer"),
|
||||
line.as_bytes(),
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
if pos >= flat.0.start && pos < flat.0.end {
|
||||
let new_span = Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1,
|
||||
};
|
||||
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - flat.0.start);
|
||||
|
||||
if prefix.starts_with(b"$") {
|
||||
let mut output =
|
||||
self.complete_variables(&working_set, &prefix, new_span, offset);
|
||||
output.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
return output;
|
||||
}
|
||||
if prefix.starts_with(b"-") {
|
||||
// this might be a flag, let's see
|
||||
if let Expr::Call(call) = &expr.expr {
|
||||
let decl = working_set.get_decl(call.decl_id);
|
||||
let sig = decl.signature();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for named in &sig.named {
|
||||
if let Some(short) = named.short {
|
||||
let mut named = vec![0; short.len_utf8()];
|
||||
short.encode_utf8(&mut named);
|
||||
named.insert(0, b'-');
|
||||
if named.starts_with(&prefix) {
|
||||
output.push((
|
||||
reedline::Span {
|
||||
start: new_span.start - offset,
|
||||
end: new_span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(&named).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if named.long.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut named = named.long.as_bytes().to_vec();
|
||||
named.insert(0, b'-');
|
||||
named.insert(0, b'-');
|
||||
if named.starts_with(&prefix) {
|
||||
output.push((
|
||||
reedline::Span {
|
||||
start: new_span.start - offset,
|
||||
end: new_span.end - offset,
|
||||
},
|
||||
String::from_utf8_lossy(&named).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
output.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
match &flat.1 {
|
||||
FlatShape::Custom(decl_id) => {
|
||||
//let prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
|
||||
let mut stack = Stack::new();
|
||||
// Set up our initial config to start from
|
||||
if let Some(conf) = &self.config {
|
||||
stack.vars.insert(CONFIG_VARIABLE_ID, conf.clone());
|
||||
} else {
|
||||
stack.vars.insert(
|
||||
CONFIG_VARIABLE_ID,
|
||||
Value::Record {
|
||||
cols: vec![],
|
||||
vals: vec![],
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let result = eval_call(
|
||||
&self.engine_state,
|
||||
&mut stack,
|
||||
&Call {
|
||||
decl_id: *decl_id,
|
||||
head: new_span,
|
||||
positional: vec![],
|
||||
named: vec![],
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: true,
|
||||
},
|
||||
PipelineData::new(new_span),
|
||||
);
|
||||
|
||||
fn map_completions<'a>(
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
new_span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<(reedline::Span, String)> {
|
||||
list.filter_map(move |x| {
|
||||
let s = x.as_string();
|
||||
|
||||
match s {
|
||||
Ok(s) => Some((
|
||||
reedline::Span {
|
||||
start: new_span.start - offset,
|
||||
end: new_span.end - offset,
|
||||
},
|
||||
s,
|
||||
)),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
let (completions, options) = match result {
|
||||
Ok(pd) => {
|
||||
let value = pd.into_value(new_span);
|
||||
match &value {
|
||||
Value::Record { .. } => {
|
||||
let completions = value
|
||||
.get_data_by_key("completions")
|
||||
.and_then(|val| {
|
||||
val.as_list().ok().map(|it| {
|
||||
map_completions(
|
||||
it.iter(),
|
||||
new_span,
|
||||
offset,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
|
||||
let options =
|
||||
if let Some(Value::Record { .. }) = &options {
|
||||
let options = options.unwrap_or_default();
|
||||
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: options
|
||||
.get_data_by_key("sort")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(true),
|
||||
}
|
||||
} else {
|
||||
CompletionOptions::default()
|
||||
};
|
||||
|
||||
(completions, options)
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let completions =
|
||||
map_completions(vals.iter(), new_span, offset);
|
||||
(completions, CompletionOptions::default())
|
||||
}
|
||||
_ => (vec![], CompletionOptions::default()),
|
||||
}
|
||||
}
|
||||
_ => (vec![], CompletionOptions::default()),
|
||||
};
|
||||
|
||||
let mut completions: Vec<(reedline::Span, String)> = completions
|
||||
.into_iter()
|
||||
.filter(|it| {
|
||||
// Minimise clones for new functionality
|
||||
match (options.case_sensitive, options.positional) {
|
||||
(true, true) => it.1.as_bytes().starts_with(&prefix),
|
||||
(true, false) => it.1.contains(
|
||||
std::str::from_utf8(&prefix).unwrap_or(""),
|
||||
),
|
||||
(false, positional) => {
|
||||
let value = it.1.to_lowercase();
|
||||
let prefix = std::str::from_utf8(&prefix)
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
if positional {
|
||||
value.starts_with(&prefix)
|
||||
} else {
|
||||
value.contains(&prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if options.sort {
|
||||
completions.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut output: Vec<_> =
|
||||
file_path_completion(new_span, &prefix, &cwd)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
(
|
||||
reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
x.1,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
output.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
return output;
|
||||
}
|
||||
flat_shape => {
|
||||
let last = 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,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
let commands =
|
||||
if matches!(flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| ((new_span.end - new_span.start) == 0)
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
self.complete_commands(&working_set, new_span, offset, true)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let preceding_byte = if new_span.start > offset {
|
||||
working_set
|
||||
.get_span_contents(Span {
|
||||
start: new_span.start - 1,
|
||||
end: new_span.start,
|
||||
})
|
||||
.to_vec()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
// let prefix = working_set.get_span_contents(flat.0);
|
||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut output = file_path_completion(new_span, &prefix, &cwd)
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
if flat_idx == 0 {
|
||||
// We're in the command position
|
||||
if x.1.starts_with('"')
|
||||
&& !matches!(preceding_byte.get(0), Some(b'^'))
|
||||
{
|
||||
let trimmed = trim_quotes(x.1.as_bytes());
|
||||
let trimmed =
|
||||
String::from_utf8_lossy(trimmed).to_string();
|
||||
let expanded =
|
||||
nu_path::canonicalize_with(trimmed, &cwd);
|
||||
|
||||
if let Ok(expanded) = expanded {
|
||||
if is_executable::is_executable(expanded) {
|
||||
(x.0, format!("^{}", x.1))
|
||||
} else {
|
||||
(x.0, x.1)
|
||||
}
|
||||
} else {
|
||||
(x.0, x.1)
|
||||
}
|
||||
} else {
|
||||
(x.0, x.1)
|
||||
}
|
||||
} else {
|
||||
(x.0, x.1)
|
||||
}
|
||||
})
|
||||
.map(move |x| {
|
||||
(
|
||||
reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
x.1,
|
||||
)
|
||||
})
|
||||
.chain(subcommands.into_iter())
|
||||
.chain(commands.into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
//output.dedup_by(|a, b| a.1 == b.1);
|
||||
output.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuCompleter {
|
||||
fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
}
|
||||
|
||||
fn file_path_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
use std::path::{is_separator, Path};
|
||||
|
||||
let partial = partial.replace('\'', "");
|
||||
|
||||
let (base_dir_name, partial) = {
|
||||
// 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, rest)
|
||||
};
|
||||
|
||||
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() {
|
||||
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) {
|
||||
let mut path = format!("{}{}", base_dir_name, file_name);
|
||||
if entry.path().is_dir() {
|
||||
path.push(SEP);
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
if path.contains(' ') {
|
||||
path = format!("\'{}\'", path);
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(partial: &str, from: &str) -> bool {
|
||||
from.to_ascii_lowercase()
|
||||
.starts_with(&partial.to_ascii_lowercase())
|
||||
}
|
84
crates/nu-cli/src/config_files.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
||||
use nu_protocol::{PipelineData, Span};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
const PLUGIN_FILE: &str = "plugin.nu";
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
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, 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, storage_path: &str) {
|
||||
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 delta in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
crates/nu-cli/src/errors.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use miette::{LabeledSpan, MietteHandler, ReportHandler, Severity, SourceCode};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use thiserror::Error;
|
||||
|
||||
/// This error exists so that we can defer SourceCode handling. It simply
|
||||
/// forwards most methods, except for `.source_code()`, which we provide.
|
||||
#[derive(Error)]
|
||||
#[error("{0}")]
|
||||
pub struct CliError<'src>(
|
||||
pub &'src (dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
pub &'src StateWorkingSet<'src>,
|
||||
);
|
||||
|
||||
impl std::fmt::Debug for CliError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
MietteHandler::default().debug(self, f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> miette::Diagnostic for CliError<'src> {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.code()
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
self.0.severity()
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.help()
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
self.0.url()
|
||||
}
|
||||
|
||||
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
|
||||
self.0.labels()
|
||||
}
|
||||
|
||||
// Finally, we redirect the source_code method to our own source.
|
||||
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||
if let Some(source_code) = self.0.source_code() {
|
||||
Some(source_code)
|
||||
} else {
|
||||
Some(&self.1)
|
||||
}
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
|
||||
self.0.related()
|
||||
}
|
||||
}
|
145
crates/nu-cli/src/eval_file.rs
Normal file
@ -0,0 +1,145 @@
|
||||
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::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
// Make a note of the exceptions we see for externals that look like math expressions
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
|
||||
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").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: &EngineState,
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
config: &Config,
|
||||
) {
|
||||
let exit_code = match &mut pipeline_data {
|
||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match engine_state.find_decl("table".as_bytes()) {
|
||||
Some(decl_id) => {
|
||||
let table = engine_state.get_decl(decl_id).run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
for item in table {
|
||||
let stdout = std::io::stdout();
|
||||
|
||||
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');
|
||||
|
||||
match stdout.lock().write_all(out.as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(err) => eprintln!("{}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for item in pipeline_data {
|
||||
let stdout = std::io::stdout();
|
||||
|
||||
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');
|
||||
|
||||
match stdout.lock().write_all(out.as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(err) => eprintln!("{}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let _: Vec<_> = exit_code.into_iter().collect();
|
||||
}
|
||||
}
|
@ -1,473 +0,0 @@
|
||||
use rustyline::{KeyCode as RustyKeyCode, Modifiers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn convert_keyevent(key_event: KeyCode, modifiers: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
match key_event {
|
||||
KeyCode::UnknownEscSeq => convert_to_rl_keyevent(RustyKeyCode::UnknownEscSeq, modifiers),
|
||||
KeyCode::Backspace => convert_to_rl_keyevent(RustyKeyCode::Backspace, modifiers),
|
||||
KeyCode::BackTab => convert_to_rl_keyevent(RustyKeyCode::BackTab, modifiers),
|
||||
KeyCode::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteStart, modifiers)
|
||||
}
|
||||
KeyCode::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteEnd, modifiers)
|
||||
}
|
||||
KeyCode::Char(c) => convert_to_rl_keyevent(RustyKeyCode::Char(c), modifiers),
|
||||
KeyCode::Delete => convert_to_rl_keyevent(RustyKeyCode::Delete, modifiers),
|
||||
KeyCode::Down => convert_to_rl_keyevent(RustyKeyCode::Down, modifiers),
|
||||
KeyCode::End => convert_to_rl_keyevent(RustyKeyCode::End, modifiers),
|
||||
KeyCode::Enter => convert_to_rl_keyevent(RustyKeyCode::Enter, modifiers),
|
||||
KeyCode::Esc => convert_to_rl_keyevent(RustyKeyCode::Esc, modifiers),
|
||||
KeyCode::F(u) => convert_to_rl_keyevent(RustyKeyCode::F(u), modifiers),
|
||||
KeyCode::Home => convert_to_rl_keyevent(RustyKeyCode::Home, modifiers),
|
||||
KeyCode::Insert => convert_to_rl_keyevent(RustyKeyCode::Insert, modifiers),
|
||||
KeyCode::Left => convert_to_rl_keyevent(RustyKeyCode::Left, modifiers),
|
||||
KeyCode::Null => convert_to_rl_keyevent(RustyKeyCode::Null, modifiers),
|
||||
KeyCode::PageDown => convert_to_rl_keyevent(RustyKeyCode::PageDown, modifiers),
|
||||
KeyCode::PageUp => convert_to_rl_keyevent(RustyKeyCode::PageUp, modifiers),
|
||||
KeyCode::Right => convert_to_rl_keyevent(RustyKeyCode::Right, modifiers),
|
||||
KeyCode::Tab => convert_to_rl_keyevent(RustyKeyCode::Tab, modifiers),
|
||||
KeyCode::Up => convert_to_rl_keyevent(RustyKeyCode::Up, modifiers),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_rl_keyevent(
|
||||
key_code: RustyKeyCode,
|
||||
modifier: Option<Modifiers>,
|
||||
) -> rustyline::KeyEvent {
|
||||
rustyline::KeyEvent {
|
||||
0: key_code,
|
||||
1: modifier.unwrap_or(Modifiers::NONE),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_word(word: Word) -> rustyline::Word {
|
||||
match word {
|
||||
Word::Big => rustyline::Word::Big,
|
||||
Word::Emacs => rustyline::Word::Emacs,
|
||||
Word::Vi => rustyline::Word::Vi,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_at(at: At) -> rustyline::At {
|
||||
match at {
|
||||
At::AfterEnd => rustyline::At::AfterEnd,
|
||||
At::BeforeEnd => rustyline::At::BeforeEnd,
|
||||
At::Start => rustyline::At::Start,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_char_search(search: CharSearch) -> rustyline::CharSearch {
|
||||
match search {
|
||||
CharSearch::Backward(c) => rustyline::CharSearch::Backward(c),
|
||||
CharSearch::BackwardAfter(c) => rustyline::CharSearch::BackwardAfter(c),
|
||||
CharSearch::Forward(c) => rustyline::CharSearch::Forward(c),
|
||||
CharSearch::ForwardBefore(c) => rustyline::CharSearch::ForwardBefore(c),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_movement(movement: Movement) -> rustyline::Movement {
|
||||
match movement {
|
||||
Movement::BackwardChar(u) => rustyline::Movement::BackwardChar(u),
|
||||
Movement::BackwardWord { repeat, word } => {
|
||||
rustyline::Movement::BackwardWord(repeat, convert_word(word))
|
||||
}
|
||||
Movement::BeginningOfBuffer => rustyline::Movement::BeginningOfBuffer,
|
||||
Movement::BeginningOfLine => rustyline::Movement::BeginningOfLine,
|
||||
Movement::EndOfBuffer => rustyline::Movement::EndOfBuffer,
|
||||
Movement::EndOfLine => rustyline::Movement::EndOfLine,
|
||||
Movement::ForwardChar(u) => rustyline::Movement::ForwardChar(u),
|
||||
Movement::ForwardWord { repeat, at, word } => {
|
||||
rustyline::Movement::ForwardWord(repeat, convert_at(at), convert_word(word))
|
||||
}
|
||||
Movement::LineDown(u) => rustyline::Movement::LineDown(u),
|
||||
Movement::LineUp(u) => rustyline::Movement::LineUp(u),
|
||||
Movement::ViCharSearch { repeat, search } => {
|
||||
rustyline::Movement::ViCharSearch(repeat, convert_char_search(search))
|
||||
}
|
||||
Movement::ViFirstPrint => rustyline::Movement::ViFirstPrint,
|
||||
Movement::WholeBuffer => rustyline::Movement::WholeBuffer,
|
||||
Movement::WholeLine => rustyline::Movement::WholeLine,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_anchor(anchor: Anchor) -> rustyline::Anchor {
|
||||
match anchor {
|
||||
Anchor::After => rustyline::Anchor::After,
|
||||
Anchor::Before => rustyline::Anchor::Before,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
match cmd {
|
||||
Cmd::Abort => rustyline::Cmd::Abort,
|
||||
Cmd::AcceptLine => rustyline::Cmd::AcceptLine,
|
||||
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine {
|
||||
accept_in_the_middle: false,
|
||||
},
|
||||
Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory,
|
||||
Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord,
|
||||
Cmd::ClearScreen => rustyline::Cmd::ClearScreen,
|
||||
Cmd::Complete => rustyline::Cmd::Complete,
|
||||
Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward,
|
||||
Cmd::CompleteHint => rustyline::Cmd::CompleteHint,
|
||||
Cmd::Dedent(movement) => rustyline::Cmd::Dedent(convert_movement(movement)),
|
||||
Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord,
|
||||
Cmd::EndOfFile => rustyline::Cmd::EndOfFile,
|
||||
Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory,
|
||||
Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory,
|
||||
Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward,
|
||||
Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward,
|
||||
Cmd::Indent(movement) => rustyline::Cmd::Indent(convert_movement(movement)),
|
||||
Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string),
|
||||
Cmd::Interrupt => rustyline::Cmd::Interrupt,
|
||||
Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)),
|
||||
Cmd::LineDownOrNextHistory(u) => rustyline::Cmd::LineDownOrNextHistory(u),
|
||||
Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u),
|
||||
Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)),
|
||||
Cmd::NextHistory => rustyline::Cmd::NextHistory,
|
||||
Cmd::Newline => rustyline::Cmd::Newline,
|
||||
Cmd::Noop => rustyline::Cmd::Noop,
|
||||
Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c),
|
||||
#[cfg(windows)]
|
||||
Cmd::PasteFromClipboard => rustyline::Cmd::PasteFromClipboard,
|
||||
Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory,
|
||||
Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert,
|
||||
Cmd::Replace {
|
||||
movement,
|
||||
replacement,
|
||||
} => rustyline::Cmd::Replace(convert_movement(movement), replacement),
|
||||
Cmd::ReplaceChar { repeat, ch } => rustyline::Cmd::ReplaceChar(repeat, ch),
|
||||
Cmd::ReverseSearchHistory => rustyline::Cmd::ReverseSearchHistory,
|
||||
Cmd::SelfInsert { repeat, ch } => rustyline::Cmd::SelfInsert(repeat, ch),
|
||||
Cmd::Suspend => rustyline::Cmd::Suspend,
|
||||
Cmd::TransposeChars => rustyline::Cmd::TransposeChars,
|
||||
Cmd::TransposeWords(u) => rustyline::Cmd::TransposeWords(u),
|
||||
Cmd::Undo(u) => rustyline::Cmd::Undo(u),
|
||||
Cmd::Unknown => rustyline::Cmd::Unknown,
|
||||
Cmd::UpcaseWord => rustyline::Cmd::UpcaseWord,
|
||||
Cmd::ViYankTo(movement) => rustyline::Cmd::ViYankTo(convert_movement(movement)),
|
||||
Cmd::Yank { repeat, anchor } => rustyline::Cmd::Yank(repeat, convert_anchor(anchor)),
|
||||
Cmd::YankPop => rustyline::Cmd::YankPop,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
|
||||
let rusty_modifiers = match keybinding.modifiers {
|
||||
Some(mods) => match mods {
|
||||
NuModifiers::Ctrl => Some(Modifiers::CTRL),
|
||||
NuModifiers::Alt => Some(Modifiers::ALT),
|
||||
NuModifiers::Shift => Some(Modifiers::SHIFT),
|
||||
NuModifiers::None => Some(Modifiers::NONE),
|
||||
NuModifiers::CtrlShift => Some(Modifiers::CTRL_SHIFT),
|
||||
NuModifiers::AltShift => Some(Modifiers::ALT_SHIFT),
|
||||
NuModifiers::CtrlAlt => Some(Modifiers::CTRL_ALT),
|
||||
NuModifiers::CtrlAltShift => Some(Modifiers::CTRL_ALT_SHIFT),
|
||||
// _ => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
(
|
||||
convert_keyevent(keybinding.key, rusty_modifiers),
|
||||
convert_cmd(keybinding.binding),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum KeyCode {
|
||||
/// Unsupported escape sequence (on unix platform)
|
||||
UnknownEscSeq,
|
||||
/// ⌫ or `KeyEvent::Ctrl('H')`
|
||||
Backspace,
|
||||
/// ⇤ (usually Shift-Tab)
|
||||
BackTab,
|
||||
/// Paste (on unix platform)
|
||||
BracketedPasteStart,
|
||||
/// Paste (on unix platform)
|
||||
BracketedPasteEnd,
|
||||
/// Single char
|
||||
Char(char),
|
||||
/// ⌦
|
||||
Delete,
|
||||
/// ↓ arrow key
|
||||
Down,
|
||||
/// ⇲
|
||||
End,
|
||||
/// ↵ or `KeyEvent::Ctrl('M')`
|
||||
Enter,
|
||||
/// Escape or `KeyEvent::Ctrl('[')`
|
||||
Esc,
|
||||
/// Function key
|
||||
F(u8),
|
||||
/// ⇱
|
||||
Home,
|
||||
/// Insert key
|
||||
Insert,
|
||||
/// ← arrow key
|
||||
Left,
|
||||
// /// `KeyEvent::Char('\0')`
|
||||
Null,
|
||||
/// ⇟
|
||||
PageDown,
|
||||
/// ⇞
|
||||
PageUp,
|
||||
/// → arrow key
|
||||
Right,
|
||||
/// ⇥ or `KeyEvent::Ctrl('I')`
|
||||
Tab,
|
||||
/// ↑ arrow key
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Cmd {
|
||||
/// abort
|
||||
Abort, // Miscellaneous Command
|
||||
/// accept-line
|
||||
AcceptLine,
|
||||
/// beginning-of-history
|
||||
BeginningOfHistory,
|
||||
/// capitalize-word
|
||||
CapitalizeWord,
|
||||
/// clear-screen
|
||||
ClearScreen,
|
||||
/// complete
|
||||
Complete,
|
||||
/// complete-backward
|
||||
CompleteBackward,
|
||||
/// complete-hint
|
||||
CompleteHint,
|
||||
/// Dedent current line
|
||||
Dedent(Movement),
|
||||
/// downcase-word
|
||||
DowncaseWord,
|
||||
/// vi-eof-maybe
|
||||
EndOfFile,
|
||||
/// end-of-history
|
||||
EndOfHistory,
|
||||
/// forward-search-history
|
||||
ForwardSearchHistory,
|
||||
/// history-search-backward
|
||||
HistorySearchBackward,
|
||||
/// history-search-forward
|
||||
HistorySearchForward,
|
||||
/// Indent current line
|
||||
Indent(Movement),
|
||||
/// Insert text
|
||||
Insert { repeat: RepeatCount, string: String },
|
||||
/// Interrupt signal (Ctrl-C)
|
||||
Interrupt,
|
||||
/// backward-delete-char, backward-kill-line, backward-kill-word
|
||||
/// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
|
||||
/// vi-delete, vi-delete-to, vi-rubout
|
||||
Kill(Movement),
|
||||
/// backward-char, backward-word, beginning-of-line, end-of-line,
|
||||
/// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
|
||||
/// vi-prev-word
|
||||
Move(Movement),
|
||||
/// Inserts a newline
|
||||
Newline,
|
||||
/// next-history
|
||||
NextHistory,
|
||||
/// No action
|
||||
Noop,
|
||||
/// vi-replace
|
||||
Overwrite(char),
|
||||
/// Paste from the clipboard
|
||||
#[cfg(windows)]
|
||||
PasteFromClipboard,
|
||||
/// previous-history
|
||||
PreviousHistory,
|
||||
/// quoted-insert
|
||||
QuotedInsert,
|
||||
/// vi-change-char
|
||||
ReplaceChar { repeat: RepeatCount, ch: char },
|
||||
/// vi-change-to, vi-substitute
|
||||
Replace {
|
||||
movement: Movement,
|
||||
replacement: Option<String>,
|
||||
},
|
||||
/// reverse-search-history
|
||||
ReverseSearchHistory,
|
||||
/// self-insert
|
||||
SelfInsert { repeat: RepeatCount, ch: char },
|
||||
/// Suspend signal (Ctrl-Z on unix platform)
|
||||
Suspend,
|
||||
/// transpose-chars
|
||||
TransposeChars,
|
||||
/// transpose-words
|
||||
TransposeWords(RepeatCount),
|
||||
/// undo
|
||||
Undo(RepeatCount),
|
||||
/// Unsupported / unexpected
|
||||
Unknown,
|
||||
/// upcase-word
|
||||
UpcaseWord,
|
||||
/// vi-yank-to
|
||||
ViYankTo(Movement),
|
||||
/// yank, vi-put
|
||||
Yank { repeat: RepeatCount, anchor: Anchor },
|
||||
/// yank-pop
|
||||
YankPop,
|
||||
/// moves cursor to the line above or switches to prev history entry if
|
||||
/// the cursor is already on the first line
|
||||
LineUpOrPreviousHistory(RepeatCount),
|
||||
/// moves cursor to the line below or switches to next history entry if
|
||||
/// the cursor is already on the last line
|
||||
LineDownOrNextHistory(RepeatCount),
|
||||
/// accepts the line when cursor is at the end of the text (non including
|
||||
/// trailing whitespace), inserts newline character otherwise
|
||||
AcceptOrInsertLine,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Movement {
|
||||
/// Whole current line (not really a movement but a range)
|
||||
WholeLine,
|
||||
/// beginning-of-line
|
||||
BeginningOfLine,
|
||||
/// end-of-line
|
||||
EndOfLine,
|
||||
/// backward-word, vi-prev-word
|
||||
BackwardWord { repeat: RepeatCount, word: Word }, // Backward until start of word
|
||||
/// forward-word, vi-end-word, vi-next-word
|
||||
ForwardWord {
|
||||
repeat: RepeatCount,
|
||||
at: At,
|
||||
word: Word,
|
||||
}, // Forward until start/end of word
|
||||
/// vi-char-search
|
||||
ViCharSearch {
|
||||
repeat: RepeatCount,
|
||||
search: CharSearch,
|
||||
},
|
||||
/// vi-first-print
|
||||
ViFirstPrint,
|
||||
/// backward-char
|
||||
BackwardChar(RepeatCount),
|
||||
/// forward-char
|
||||
ForwardChar(RepeatCount),
|
||||
/// move to the same column on the previous line
|
||||
LineUp(RepeatCount),
|
||||
/// move to the same column on the next line
|
||||
LineDown(RepeatCount),
|
||||
/// Whole user input (not really a movement but a range)
|
||||
WholeBuffer,
|
||||
/// beginning-of-buffer
|
||||
BeginningOfBuffer,
|
||||
/// end-of-buffer
|
||||
EndOfBuffer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
enum InputMode {
|
||||
/// Vi Command/Alternate
|
||||
Command,
|
||||
/// Insert/Input mode
|
||||
Insert,
|
||||
/// Overwrite mode
|
||||
Replace,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Word {
|
||||
/// non-blanks characters
|
||||
Big,
|
||||
/// alphanumeric characters
|
||||
Emacs,
|
||||
/// alphanumeric (and '_') characters
|
||||
Vi,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum At {
|
||||
/// Start of word.
|
||||
Start,
|
||||
/// Before end of word.
|
||||
BeforeEnd,
|
||||
/// After end of word.
|
||||
AfterEnd,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Anchor {
|
||||
/// After cursor
|
||||
After,
|
||||
/// Before cursor
|
||||
Before,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum CharSearch {
|
||||
/// Forward search
|
||||
Forward(char),
|
||||
/// Forward search until
|
||||
ForwardBefore(char),
|
||||
/// Backward search
|
||||
Backward(char),
|
||||
/// Backward search until
|
||||
BackwardAfter(char),
|
||||
}
|
||||
|
||||
/// The set of modifier keys that were triggered along with a key press.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum NuModifiers {
|
||||
/// Control modifier
|
||||
#[serde(alias = "CTRL")]
|
||||
Ctrl = 8,
|
||||
/// Escape or Alt modifier
|
||||
#[serde(alias = "ALT")]
|
||||
Alt = 4,
|
||||
/// Shift modifier
|
||||
#[serde(alias = "SHIFT")]
|
||||
Shift = 2,
|
||||
/// No modifier
|
||||
#[serde(alias = "NONE")]
|
||||
None = 0,
|
||||
/// Ctrl + Shift
|
||||
#[serde(alias = "CTRL_SHIFT")]
|
||||
CtrlShift = 10,
|
||||
/// Alt + Shift
|
||||
#[serde(alias = "ALT_SHIFT")]
|
||||
AltShift = 6,
|
||||
/// Ctrl + Alt
|
||||
#[serde(alias = "CTRL_ALT")]
|
||||
CtrlAlt = 12,
|
||||
/// Ctrl + Alt + Shift
|
||||
#[serde(alias = "CTRL_ALT_SHIFT")]
|
||||
CtrlAltShift = 14,
|
||||
}
|
||||
|
||||
/// The number of times one command should be repeated.
|
||||
pub type RepeatCount = usize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Keybinding {
|
||||
key: KeyCode,
|
||||
modifiers: Option<NuModifiers>,
|
||||
binding: Cmd,
|
||||
}
|
||||
|
||||
type Keybindings = Vec<Keybinding>;
|
||||
|
||||
pub(crate) fn load_keybindings(
|
||||
rl: &mut rustyline::Editor<crate::shell::Helper>,
|
||||
) -> Result<(), nu_errors::ShellError> {
|
||||
let filename = nu_data::keybinding::keybinding_path()?;
|
||||
let contents = std::fs::read_to_string(filename);
|
||||
|
||||
// Silently fail if there is no file there
|
||||
if let Ok(contents) = contents {
|
||||
let keybindings: Keybindings = serde_yaml::from_str(&contents)?;
|
||||
// eprintln!("{:#?}", keybindings);
|
||||
for keybinding in keybindings {
|
||||
let (k, b) = convert_keybinding(keybinding);
|
||||
// eprintln!("{:?} {:?}", k, b);
|
||||
|
||||
rl.bind_sequence(k, b);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,15 +1,33 @@
|
||||
pub mod app;
|
||||
mod cli;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
mod line_editor;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod shell;
|
||||
mod commands;
|
||||
mod completions;
|
||||
mod config_files;
|
||||
mod errors;
|
||||
mod eval_file;
|
||||
mod nu_highlight;
|
||||
mod print;
|
||||
mod prompt;
|
||||
mod prompt_update;
|
||||
mod reedline_config;
|
||||
mod repl;
|
||||
mod syntax_highlight;
|
||||
mod util;
|
||||
mod validation;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub use crate::cli::cli;
|
||||
pub use commands::evaluate_commands;
|
||||
pub use completions::NuCompleter;
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use errors::CliError;
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::print_pipeline_data;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
pub use crate::app::App;
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
|
||||
pub use nu_command::create_default_context;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::add_plugin_file;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::read_plugin_file;
|
||||
|
@ -1,276 +0,0 @@
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_errors::ShellError;
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_engine::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::keybinding::{convert_keyevent, KeyCode};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
line_buffer::LineBuffer,
|
||||
At, Cmd, ConditionalEventHandler, Editor, EventHandler, Modifiers, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
struct PartialCompleteHintHandler;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
impl ConditionalEventHandler for PartialCompleteHintHandler {
|
||||
fn handle(
|
||||
&self,
|
||||
_evt: &rustyline::Event,
|
||||
_n: rustyline::RepeatCount,
|
||||
_positive: bool,
|
||||
ctx: &rustyline::EventContext,
|
||||
) -> Option<Cmd> {
|
||||
Some(match ctx.hint_text() {
|
||||
Some(hint_text) if ctx.pos() == ctx.line().len() => {
|
||||
let mut line_buffer = LineBuffer::with_capacity(hint_text.len());
|
||||
line_buffer.update(hint_text, 0);
|
||||
line_buffer.move_to_next_word(At::AfterEnd, Word::Vi, 1);
|
||||
|
||||
let text = hint_text[0..line_buffer.pos()].to_string();
|
||||
|
||||
Cmd::Insert(1, text)
|
||||
}
|
||||
_ => Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder()
|
||||
.check_cursor_position(true)
|
||||
.color_mode(ColorMode::Forced)
|
||||
.history_ignore_dups(false)
|
||||
.max_history_size(10_000)
|
||||
.build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
//M modifier, E KeyEvent, K KeyCode
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Left, Some(Modifiers::CTRL)),
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Right, Some(Modifiers::CTRL)),
|
||||
EventHandler::Conditional(Box::new(PartialCompleteHintHandler)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::BracketedPasteStart, None),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn nu_line_editor_helper(
|
||||
context: &EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn rustyline_hinter(
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.as_bool() == Ok(false) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c().clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c().load(Ordering::SeqCst) {
|
||||
_context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
63
crates/nu-cli/src/nu_highlight.rs
Normal file
@ -0,0 +1,63 @@
|
||||
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 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 = stack.get_config()?;
|
||||
|
||||
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,
|
||||
}]
|
||||
}
|
||||
}
|
57
crates/nu-cli/src/print.rs
Normal file
@ -0,0 +1,57 @@
|
||||
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")
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Prints the values given"
|
||||
}
|
||||
|
||||
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 head = call.head;
|
||||
|
||||
for arg in args {
|
||||
crate::util::print_pipeline_data(arg.into_pipeline_data(), engine_state, stack)?;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
143
crates/nu-cli/src/prompt.rs
Normal file
@ -0,0 +1,143 @@
|
||||
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: String,
|
||||
default_vi_insert_prompt_indicator: String,
|
||||
default_vi_normal_prompt_indicator: String,
|
||||
default_multiline_indicator: 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: "〉".to_string(),
|
||||
default_vi_insert_prompt_indicator: ": ".to_string(),
|
||||
default_vi_normal_prompt_indicator: "〉".to_string(),
|
||||
default_multiline_indicator: "::: ".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
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: String) {
|
||||
self.default_prompt_indicator = prompt_indicator_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: String) {
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: String) {
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: 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: String,
|
||||
prompt_multiline_indicator_string: String,
|
||||
prompt_vi: (String, 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_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
self.default_multiline_indicator = prompt_multiline_indicator_string;
|
||||
}
|
||||
|
||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||
format!("({})", str)
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
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 => self.default_prompt_indicator.as_str().into(),
|
||||
PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(),
|
||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||
PromptViMode::Normal => self.default_vi_normal_prompt_indicator.as_str().into(),
|
||||
PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(),
|
||||
},
|
||||
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
Cow::Borrowed(self.default_multiline_indicator.as_str())
|
||||
}
|
||||
|
||||
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
|
||||
))
|
||||
}
|
||||
}
|
193
crates/nu-cli/src/prompt_update.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use crate::util::report_error;
|
||||
use crate::NushellPrompt;
|
||||
use log::info;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_parser::parse;
|
||||
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";
|
||||
|
||||
pub(crate) fn get_prompt_indicators(
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
is_perf_true: bool,
|
||||
) -> (String, String, String, String) {
|
||||
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
||||
Some(pi) => pi.into_string("", config),
|
||||
None => "〉".to_string(),
|
||||
};
|
||||
|
||||
let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) {
|
||||
Some(pvii) => pvii.into_string("", config),
|
||||
None => ": ".to_string(),
|
||||
};
|
||||
|
||||
let prompt_vi_normal = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_NORMAL) {
|
||||
Some(pviv) => pviv.into_string("", config),
|
||||
None => "〉".to_string(),
|
||||
};
|
||||
|
||||
let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) {
|
||||
Some(pm) => pm.into_string("", config),
|
||||
None => "::: ".to_string(),
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_indicators {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
prompt_indicator,
|
||||
prompt_vi_insert,
|
||||
prompt_vi_normal,
|
||||
prompt_multiline,
|
||||
)
|
||||
}
|
||||
|
||||
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 { val: source, .. } => {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (block, _) = parse(&mut working_set, None, source.as_bytes(), true, &[]);
|
||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||
let ret_val = eval_subexpression(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||
)
|
||||
.ok();
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_string (string) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
ret_val
|
||||
}
|
||||
_ => 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 {
|
||||
// get the other indicators
|
||||
let (
|
||||
prompt_indicator_string,
|
||||
prompt_vi_insert_string,
|
||||
prompt_vi_normal_string,
|
||||
prompt_multiline_string,
|
||||
) = get_prompt_indicators(config, engine_state, stack, is_perf_true);
|
||||
|
||||
let mut stack = stack.clone();
|
||||
|
||||
// apply the other indicators
|
||||
nu_prompt.update_all_prompt_strings(
|
||||
get_prompt_string(
|
||||
PROMPT_COMMAND,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
),
|
||||
get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
),
|
||||
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
|
||||
}
|
781
crates/nu-cli/src/reedline_config.rs
Normal file
@ -0,0 +1,781 @@
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use nu_color_config::lookup_ansi_color_style;
|
||||
use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||
CompletionMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent,
|
||||
};
|
||||
|
||||
// Creates an input object for the completion menu based on the dictionary
|
||||
// stored in the config variable
|
||||
pub(crate) fn add_completion_menu(line_editor: Reedline, config: &Config) -> Reedline {
|
||||
let mut completion_menu = CompletionMenu::default();
|
||||
|
||||
completion_menu = match config
|
||||
.menu_config
|
||||
.get("columns")
|
||||
.and_then(|value| value.as_integer().ok())
|
||||
{
|
||||
Some(value) => completion_menu.with_columns(value as u16),
|
||||
None => completion_menu,
|
||||
};
|
||||
|
||||
completion_menu = completion_menu.with_column_width(
|
||||
config
|
||||
.menu_config
|
||||
.get("col_width")
|
||||
.and_then(|value| value.as_integer().ok())
|
||||
.map(|value| value as usize),
|
||||
);
|
||||
|
||||
completion_menu = match config
|
||||
.menu_config
|
||||
.get("col_padding")
|
||||
.and_then(|value| value.as_integer().ok())
|
||||
{
|
||||
Some(value) => completion_menu.with_column_padding(value as usize),
|
||||
None => completion_menu,
|
||||
};
|
||||
|
||||
completion_menu = match config
|
||||
.menu_config
|
||||
.get("text_style")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => completion_menu.with_text_style(lookup_ansi_color_style(&value)),
|
||||
None => completion_menu,
|
||||
};
|
||||
|
||||
completion_menu = match config
|
||||
.menu_config
|
||||
.get("selected_text_style")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => completion_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
|
||||
None => completion_menu,
|
||||
};
|
||||
|
||||
completion_menu = match config
|
||||
.menu_config
|
||||
.get("marker")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => completion_menu.with_marker(value),
|
||||
None => completion_menu,
|
||||
};
|
||||
|
||||
line_editor.with_menu(Box::new(completion_menu))
|
||||
}
|
||||
|
||||
// Creates an input object for the history menu based on the dictionary
|
||||
// stored in the config variable
|
||||
pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedline {
|
||||
let mut history_menu = HistoryMenu::default();
|
||||
|
||||
history_menu = match config
|
||||
.history_config
|
||||
.get("page_size")
|
||||
.and_then(|value| value.as_integer().ok())
|
||||
{
|
||||
Some(value) => history_menu.with_page_size(value as usize),
|
||||
None => history_menu,
|
||||
};
|
||||
|
||||
history_menu = match config
|
||||
.history_config
|
||||
.get("selector")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => {
|
||||
let char = value.chars().next().unwrap_or('!');
|
||||
history_menu.with_selection_char(char)
|
||||
}
|
||||
None => history_menu,
|
||||
};
|
||||
|
||||
history_menu = match config
|
||||
.history_config
|
||||
.get("text_style")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => history_menu.with_text_style(lookup_ansi_color_style(&value)),
|
||||
None => history_menu,
|
||||
};
|
||||
|
||||
history_menu = match config
|
||||
.history_config
|
||||
.get("selected_text_style")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => history_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
|
||||
None => history_menu,
|
||||
};
|
||||
|
||||
history_menu = match config
|
||||
.history_config
|
||||
.get("marker")
|
||||
.and_then(|value| value.as_string().ok())
|
||||
{
|
||||
Some(value) => history_menu.with_marker(value),
|
||||
None => history_menu,
|
||||
};
|
||||
|
||||
line_editor.with_menu(Box::new(history_menu))
|
||||
}
|
||||
|
||||
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char('x'),
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::Menu("history_menu".to_string()),
|
||||
ReedlineEvent::MenuPageNext,
|
||||
]),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char('z'),
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::MenuPagePrevious,
|
||||
ReedlineEvent::Edit(vec![EditCommand::Undo]),
|
||||
]),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||
ReedlineEvent::MenuNext,
|
||||
]),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::SHIFT,
|
||||
KeyCode::BackTab,
|
||||
ReedlineEvent::MenuPrevious,
|
||||
);
|
||||
}
|
||||
|
||||
pub enum KeybindingsMode {
|
||||
Emacs(Keybindings),
|
||||
Vi {
|
||||
insert_keybindings: Keybindings,
|
||||
normal_keybindings: Keybindings,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, ShellError> {
|
||||
let parsed_keybindings = &config.keybindings;
|
||||
|
||||
let mut emacs_keybindings = default_emacs_keybindings();
|
||||
let mut insert_keybindings = default_vi_insert_keybindings();
|
||||
let mut normal_keybindings = default_vi_normal_keybindings();
|
||||
|
||||
for keybinding in parsed_keybindings {
|
||||
add_keybinding(
|
||||
&keybinding.mode,
|
||||
keybinding,
|
||||
config,
|
||||
&mut emacs_keybindings,
|
||||
&mut insert_keybindings,
|
||||
&mut normal_keybindings,
|
||||
)?
|
||||
}
|
||||
|
||||
match config.edit_mode.as_str() {
|
||||
"emacs" => {
|
||||
add_menu_keybindings(&mut emacs_keybindings);
|
||||
|
||||
Ok(KeybindingsMode::Emacs(emacs_keybindings))
|
||||
}
|
||||
_ => {
|
||||
add_menu_keybindings(&mut insert_keybindings);
|
||||
add_menu_keybindings(&mut normal_keybindings);
|
||||
|
||||
Ok(KeybindingsMode::Vi {
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_keybinding(
|
||||
mode: &Value,
|
||||
keybinding: &ParsedKeybinding,
|
||||
config: &Config,
|
||||
emacs_keybindings: &mut Keybindings,
|
||||
insert_keybindings: &mut Keybindings,
|
||||
normal_keybindings: &mut Keybindings,
|
||||
) -> Result<(), ShellError> {
|
||||
match &mode {
|
||||
Value::String { val, span } => match val.as_str() {
|
||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
||||
m => Err(ShellError::UnsupportedConfigValue(
|
||||
"emacs, vi_insert or vi_normal".to_string(),
|
||||
m.to_string(),
|
||||
*span,
|
||||
)),
|
||||
},
|
||||
Value::List { vals, .. } => {
|
||||
for inner_mode in vals {
|
||||
add_keybinding(
|
||||
inner_mode,
|
||||
keybinding,
|
||||
config,
|
||||
emacs_keybindings,
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue(
|
||||
"string or list of strings".to_string(),
|
||||
v.into_abbreviated_string(config),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_parsed_keybinding(
|
||||
keybindings: &mut Keybindings,
|
||||
keybinding: &ParsedKeybinding,
|
||||
config: &Config,
|
||||
) -> Result<(), ShellError> {
|
||||
let modifier = match keybinding
|
||||
.modifier
|
||||
.into_string("", config)
|
||||
.to_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"control" => KeyModifiers::CONTROL,
|
||||
"shift" => KeyModifiers::SHIFT,
|
||||
"alt" => KeyModifiers::ALT,
|
||||
"none" => KeyModifiers::NONE,
|
||||
"control | shift" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
||||
"control | alt" => KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
"control | alt | shift" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||
keybinding.modifier.into_abbreviated_string(config),
|
||||
keybinding.modifier.span()?,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let keycode = match keybinding
|
||||
.keycode
|
||||
.into_string("", config)
|
||||
.to_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"enter" => KeyCode::Enter,
|
||||
c if c.starts_with("char_") => {
|
||||
let mut char_iter = c.chars().skip(5);
|
||||
let pos1 = char_iter.next();
|
||||
let pos2 = char_iter.next();
|
||||
|
||||
let char = match (pos1, pos2) {
|
||||
(Some(char), None) => Ok(char),
|
||||
_ => Err(ShellError::UnsupportedConfigValue(
|
||||
"char_<CHAR: unicode codepoint>".to_string(),
|
||||
c.to_string(),
|
||||
keybinding.keycode.span()?,
|
||||
)),
|
||||
}?;
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
"right" => KeyCode::Right,
|
||||
"home" => KeyCode::Home,
|
||||
"end" => KeyCode::End,
|
||||
"pageup" => KeyCode::PageUp,
|
||||
"pagedown" => KeyCode::PageDown,
|
||||
"tab" => KeyCode::Tab,
|
||||
"backtab" => KeyCode::BackTab,
|
||||
"delete" => KeyCode::Delete,
|
||||
"insert" => KeyCode::Insert,
|
||||
c if c.starts_with('f') => {
|
||||
let fn_num: u8 = c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| matches!(num, 1..=12))
|
||||
.ok_or(ShellError::UnsupportedConfigValue(
|
||||
"(f1|f2|...|f12)".to_string(),
|
||||
format!("unknown function key: {}", c),
|
||||
keybinding.keycode.span()?,
|
||||
))?;
|
||||
KeyCode::F(fn_num)
|
||||
}
|
||||
"null" => KeyCode::Null,
|
||||
"esc" | "escape" => KeyCode::Esc,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"crossterm KeyCode".to_string(),
|
||||
keybinding.keycode.into_abbreviated_string(config),
|
||||
keybinding.keycode.span()?,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let event = parse_event(&keybinding.event, config)?;
|
||||
|
||||
keybindings.add_binding(modifier, keycode, event);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum EventType<'config> {
|
||||
Send(&'config Value),
|
||||
Edit(&'config Value),
|
||||
Until(&'config Value),
|
||||
}
|
||||
|
||||
impl<'config> EventType<'config> {
|
||||
fn try_from_columns(
|
||||
cols: &'config [String],
|
||||
vals: &'config [Value],
|
||||
span: &'config Span,
|
||||
) -> Result<Self, ShellError> {
|
||||
extract_value("send", cols, vals, span)
|
||||
.map(Self::Send)
|
||||
.or_else(|_| extract_value("edit", cols, vals, span).map(Self::Edit))
|
||||
.or_else(|_| extract_value("until", cols, vals, span).map(Self::Until))
|
||||
.map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), *span))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_event(value: &Value, config: &Config) -> Result<ReedlineEvent, ShellError> {
|
||||
match value {
|
||||
Value::Record { cols, vals, span } => {
|
||||
match EventType::try_from_columns(cols, vals, span)? {
|
||||
EventType::Send(value) => event_from_record(
|
||||
value.into_string("", config).to_lowercase().as_str(),
|
||||
cols,
|
||||
vals,
|
||||
config,
|
||||
span,
|
||||
),
|
||||
EventType::Edit(value) => {
|
||||
let edit = edit_from_record(
|
||||
value.into_string("", config).to_lowercase().as_str(),
|
||||
cols,
|
||||
vals,
|
||||
config,
|
||||
span,
|
||||
)?;
|
||||
Ok(ReedlineEvent::Edit(vec![edit]))
|
||||
}
|
||||
EventType::Until(value) => match value {
|
||||
Value::List { vals, .. } => {
|
||||
let events = vals
|
||||
.iter()
|
||||
.map(|value| parse_event(value, config))
|
||||
.collect::<Result<Vec<ReedlineEvent>, ShellError>>()?;
|
||||
|
||||
Ok(ReedlineEvent::UntilFound(events))
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue(
|
||||
"list of events".to_string(),
|
||||
v.into_abbreviated_string(config),
|
||||
v.span()?,
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let events = vals
|
||||
.iter()
|
||||
.map(|value| parse_event(value, config))
|
||||
.collect::<Result<Vec<ReedlineEvent>, ShellError>>()?;
|
||||
|
||||
Ok(ReedlineEvent::Multiple(events))
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue(
|
||||
"record or list of records".to_string(),
|
||||
v.into_abbreviated_string(config),
|
||||
v.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_from_record(
|
||||
name: &str,
|
||||
cols: &[String],
|
||||
vals: &[Value],
|
||||
config: &Config,
|
||||
span: &Span,
|
||||
) -> Result<ReedlineEvent, ShellError> {
|
||||
let event = match name {
|
||||
"none" => ReedlineEvent::None,
|
||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||
"historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete,
|
||||
"ctrld" => ReedlineEvent::CtrlD,
|
||||
"ctrlc" => ReedlineEvent::CtrlC,
|
||||
"enter" => ReedlineEvent::Enter,
|
||||
"esc" | "escape" => ReedlineEvent::Esc,
|
||||
"up" => ReedlineEvent::Up,
|
||||
"down" => ReedlineEvent::Down,
|
||||
"right" => ReedlineEvent::Right,
|
||||
"left" => ReedlineEvent::Left,
|
||||
"searchhistory" => ReedlineEvent::SearchHistory,
|
||||
"nexthistory" => ReedlineEvent::NextHistory,
|
||||
"previoushistory" => ReedlineEvent::PreviousHistory,
|
||||
"repaint" => ReedlineEvent::Repaint,
|
||||
"menudown" => ReedlineEvent::MenuDown,
|
||||
"menuup" => ReedlineEvent::MenuUp,
|
||||
"menuleft" => ReedlineEvent::MenuLeft,
|
||||
"menuright" => ReedlineEvent::MenuRight,
|
||||
"menunext" => ReedlineEvent::MenuNext,
|
||||
"menuprevious" => ReedlineEvent::MenuPrevious,
|
||||
"menupagenext" => ReedlineEvent::MenuPageNext,
|
||||
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
|
||||
"menu" => {
|
||||
let menu = extract_value("name", cols, vals, span)?;
|
||||
ReedlineEvent::Menu(menu.into_string("", config))
|
||||
}
|
||||
"executehostcommand" => {
|
||||
let cmd = extract_value("cmd", cols, vals, span)?;
|
||||
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
|
||||
}
|
||||
v => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"Reedline event".to_string(),
|
||||
v.to_string(),
|
||||
*span,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
fn edit_from_record(
|
||||
name: &str,
|
||||
cols: &[String],
|
||||
vals: &[Value],
|
||||
config: &Config,
|
||||
span: &Span,
|
||||
) -> Result<EditCommand, ShellError> {
|
||||
let edit = match name {
|
||||
"movetostart" => EditCommand::MoveToStart,
|
||||
"movetolinestart" => EditCommand::MoveToLineStart,
|
||||
"movetoend" => EditCommand::MoveToEnd,
|
||||
"movetolineend" => EditCommand::MoveToLineEnd,
|
||||
"moveleft" => EditCommand::MoveLeft,
|
||||
"moveright" => EditCommand::MoveRight,
|
||||
"movewordleft" => EditCommand::MoveWordLeft,
|
||||
"movewordright" => EditCommand::MoveWordRight,
|
||||
"insertchar" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::InsertChar(char)
|
||||
}
|
||||
"insertstring" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
EditCommand::InsertString(value.into_string("", config))
|
||||
}
|
||||
"backspace" => EditCommand::Backspace,
|
||||
"delete" => EditCommand::Delete,
|
||||
"backspaceword" => EditCommand::BackspaceWord,
|
||||
"deleteword" => EditCommand::DeleteWord,
|
||||
"clear" => EditCommand::Clear,
|
||||
"cleartolineend" => EditCommand::ClearToLineEnd,
|
||||
"cutcurrentline" => EditCommand::CutCurrentLine,
|
||||
"cutfromstart" => EditCommand::CutFromStart,
|
||||
"cutfromlinestart" => EditCommand::CutFromLineStart,
|
||||
"cuttoend" => EditCommand::CutToEnd,
|
||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||
"cutwordleft" => EditCommand::CutWordLeft,
|
||||
"cutwordright" => EditCommand::CutWordRight,
|
||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||
"uppercaseword" => EditCommand::UppercaseWord,
|
||||
"lowercaseword" => EditCommand::LowercaseWord,
|
||||
"capitalizechar" => EditCommand::CapitalizeChar,
|
||||
"swapwords" => EditCommand::SwapWords,
|
||||
"swapgraphemes" => EditCommand::SwapGraphemes,
|
||||
"undo" => EditCommand::Undo,
|
||||
"redo" => EditCommand::Redo,
|
||||
"cutrightuntil" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::CutRightUntil(char)
|
||||
}
|
||||
"cutrightbefore" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::CutRightBefore(char)
|
||||
}
|
||||
"moverightuntil" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::MoveRightUntil(char)
|
||||
}
|
||||
"moverightbefore" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::MoveRightBefore(char)
|
||||
}
|
||||
"cutleftuntil" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::CutLeftUntil(char)
|
||||
}
|
||||
"cutleftbefore" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::CutLeftBefore(char)
|
||||
}
|
||||
"moveleftuntil" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::MoveLeftUntil(char)
|
||||
}
|
||||
"moveleftbefore" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::MoveLeftBefore(char)
|
||||
}
|
||||
e => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"reedline EditCommand".to_string(),
|
||||
e.to_string(),
|
||||
*span,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(edit)
|
||||
}
|
||||
|
||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||
let span = value.span()?;
|
||||
value
|
||||
.into_string("", config)
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or_else(|| ShellError::MissingConfigValue("char to insert".to_string(), span))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_send_event() {
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
assert!(matches!(b, EventType::Send(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
vals,
|
||||
cols,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
let config = Config::default();
|
||||
|
||||
let parsed_event = parse_event(&event, &config).unwrap();
|
||||
assert_eq!(parsed_event, ReedlineEvent::Enter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_event() {
|
||||
let cols = vec!["edit".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Clear".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
assert!(matches!(b, EventType::Edit(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
vals,
|
||||
cols,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
let config = Config::default();
|
||||
|
||||
let parsed_event = parse_event(&event, &config).unwrap();
|
||||
assert_eq!(parsed_event, ReedlineEvent::Edit(vec![EditCommand::Clear]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_menu() {
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
assert!(matches!(b, EventType::Send(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
vals,
|
||||
cols,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
let config = Config::default();
|
||||
|
||||
let parsed_event = parse_event(&event, &config).unwrap();
|
||||
assert_eq!(
|
||||
parsed_event,
|
||||
ReedlineEvent::Menu("history_menu".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_until_event() {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
|
||||
// Until event
|
||||
let cols = vec!["until".to_string()];
|
||||
let vals = vec![Value::List {
|
||||
vals: vec![menu_event, enter_event],
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
assert!(matches!(b, EventType::Until(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
let config = Config::default();
|
||||
|
||||
let parsed_event = parse_event(&event, &config).unwrap();
|
||||
assert_eq!(
|
||||
parsed_event,
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::Menu("history_menu".to_string()),
|
||||
ReedlineEvent::Enter,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_event() {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: Span::test_data(),
|
||||
};
|
||||
|
||||
// Multiple event
|
||||
let event = Value::List {
|
||||
vals: vec![menu_event, enter_event],
|
||||
span: Span::test_data(),
|
||||
};
|
||||
|
||||
let config = Config::default();
|
||||
let parsed_event = parse_event(&event, &config).unwrap();
|
||||
assert_eq!(
|
||||
parsed_event,
|
||||
ReedlineEvent::Multiple(vec![
|
||||
ReedlineEvent::Menu("history_menu".to_string()),
|
||||
ReedlineEvent::Enter,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let cols = vec!["not_exist".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
|
||||
}
|
||||
}
|
367
crates/nu-cli/src/repl.rs
Normal file
@ -0,0 +1,367 @@
|
||||
use crate::reedline_config::{add_completion_menu, add_history_menu};
|
||||
use crate::{prompt_update, reedline_config};
|
||||
use crate::{
|
||||
reedline_config::KeybindingsMode,
|
||||
util::{eval_source, report_error},
|
||||
};
|
||||
use crate::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::convert_env_values;
|
||||
use nu_parser::lex;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::PipelineData;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Config, ShellError, Span, Value, CONFIG_VARIABLE_ID,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, Vi};
|
||||
use std::path::PathBuf;
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
history_path: Option<PathBuf>,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
// use crate::logger::{configure, logger};
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
// let mut stack = nu_protocol::engine::Stack::new();
|
||||
|
||||
// First, set up env vars as strings only
|
||||
// gather_parent_env_vars(engine_state);
|
||||
|
||||
// Set up our initial config to start from
|
||||
// stack.vars.insert(
|
||||
// CONFIG_VARIABLE_ID,
|
||||
// Value::Record {
|
||||
// cols: vec![],
|
||||
// vals: vec![],
|
||||
// span: Span::new(0, 0),
|
||||
// },
|
||||
// );
|
||||
|
||||
if is_perf_true {
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// #[cfg(feature = "plugin")]
|
||||
// config_files::read_plugin_file(engine_state, &mut stack, is_perf_true);
|
||||
//
|
||||
// if is_perf_true {
|
||||
// info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||
// }
|
||||
//
|
||||
// config_files::read_config_file(engine_state, &mut stack, config_file, is_perf_true);
|
||||
// let history_path = config_files::create_history_path();
|
||||
|
||||
// logger(|builder| {
|
||||
// configure(&config.log_level, builder)?;
|
||||
// // trace_filters(self, builder)?;
|
||||
// // debug_filters(self, builder)?;
|
||||
|
||||
// Ok(())
|
||||
// })?;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Make a note of the exceptions we see for externals that look like math expressions
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
|
||||
// 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 },
|
||||
},
|
||||
);
|
||||
|
||||
loop {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config each loop {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
let config = match stack.get_config() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &e);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup line editor {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let mut line_editor = Reedline::create()
|
||||
.into_diagnostic()?
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_state.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_animation(config.animate_prompt)
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_state.clone(),
|
||||
stack.vars.get(&CONFIG_VARIABLE_ID).cloned(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring);
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
line_editor = add_completion_menu(line_editor, &config);
|
||||
line_editor = add_history_menu(line_editor, &config);
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
//FIXME: if config.use_ansi_coloring is false then we should
|
||||
// turn off the hinter but I don't see any way to do that yet.
|
||||
|
||||
let color_hm = get_color_config(&config);
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"setup history and hinter {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
line_editor = if let Some(history_path) = history_path.clone() {
|
||||
let history = std::fs::read_to_string(&history_path);
|
||||
if history.is_ok() {
|
||||
line_editor
|
||||
.with_hinter(Box::new(
|
||||
DefaultHinter::default().with_style(color_hm["hints"]),
|
||||
))
|
||||
.with_history(Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
config.max_history_size as usize,
|
||||
history_path.clone(),
|
||||
)
|
||||
.into_diagnostic()?,
|
||||
))
|
||||
.into_diagnostic()?
|
||||
} else {
|
||||
line_editor
|
||||
}
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = match reedline_config::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!());
|
||||
}
|
||||
|
||||
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);
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
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 (orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\'))
|
||||
&& 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),
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
//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)),
|
||||
);
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: format!("{}", start_time.elapsed().as_millis()),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
// FIXME: permanent state changes like this hopefully in time can be removed
|
||||
// and be replaced by just passing the cwd in where needed
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
let _ = std::env::set_current_dir(path);
|
||||
engine_state.env_vars.insert("PWD".into(), cwd);
|
||||
}
|
||||
|
||||
// Make a note of the exceptions we see for externals that look like math expressions
|
||||
let exceptions = crate::util::external_exceptions(engine_state, stack);
|
||||
engine_state.external_exceptions = exceptions;
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
Ok(Signal::CtrlL) => {
|
||||
line_editor.clear_screen().into_diagnostic()?;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
use nu_ansi_term::Color;
|
||||
use nu_completion::NuCompleter;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub struct Helper {
|
||||
completer: NuCompleter,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
context: EvaluationContext,
|
||||
pub colored_prompt: String,
|
||||
validator: NuValidator,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub(crate) fn new(
|
||||
context: EvaluationContext,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
) -> Helper {
|
||||
Helper {
|
||||
completer: NuCompleter {},
|
||||
hinter,
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
validator: NuValidator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use nu_protocol::{SignatureRegistry, VariableRegistry};
|
||||
struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
|
||||
fn signature_registry(&self) -> &dyn SignatureRegistry {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn scope(&self) -> &dyn nu_parser::ParserScope {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn source(&self) -> &EvaluationContext {
|
||||
self.as_ref()
|
||||
}
|
||||
|
||||
fn variable_registry(&self) -> &dyn VariableRegistry {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
|
||||
fn as_ref(&self) -> &EvaluationContext {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompletionSuggestion(nu_completion::Suggestion);
|
||||
|
||||
impl rustyline::completion::Candidate for CompletionSuggestion {
|
||||
fn display(&self) -> &str {
|
||||
&self.0.display
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
&self.0.replacement
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for Helper {
|
||||
type Candidate = CompletionSuggestion;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = CompletionContext(&self.context);
|
||||
let (position, suggestions) = self.completer.complete(line, pos, &ctx);
|
||||
let suggestions = suggestions.into_iter().map(CompletionSuggestion).collect();
|
||||
Ok((position, suggestions))
|
||||
}
|
||||
|
||||
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
||||
let end = line.pos();
|
||||
line.replace(start..end, elected)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
type Hint = String;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
match &self.hinter {
|
||||
Some(the_hinter) => the_hinter.hint(line, pos, ctx),
|
||||
None => Some("".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::highlight::Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> Cow<'b, str> {
|
||||
use std::borrow::Cow::Borrowed;
|
||||
|
||||
if default {
|
||||
Borrowed(&self.colored_prompt)
|
||||
} else {
|
||||
Borrowed(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let cfg = &self.context.configs().lock();
|
||||
if let Some(palette) = &cfg.syntax_config {
|
||||
Painter::paint_string(line, &self.context.scope, palette)
|
||||
} else {
|
||||
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::validate::Validator for Helper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct NuValidator {}
|
||||
|
||||
impl rustyline::validate::Validator for NuValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let src = ctx.input();
|
||||
|
||||
let (tokens, err) = nu_parser::lex(src, 0, nu_parser::NewlineMode::Normal);
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::parse_block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustyline::validate::ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
let first = iter.next()?.tag.clone();
|
||||
let last = iter.last();
|
||||
|
||||
Some(match last {
|
||||
None => first,
|
||||
Some(last) => first.until(&last.tag),
|
||||
})
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::EvaluationContext;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
213
crates/nu-cli/src/syntax_highlight.rs
Normal file
@ -0,0 +1,213 @@
|
||||
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::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
|
||||
}
|
||||
}
|
487
crates/nu-cli/src/util.rs
Normal file
@ -0,0 +1,487 @@
|
||||
use crate::CliError;
|
||||
use log::trace;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::{lex, parse, trim_quotes, Token, TokenContents};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn print_pipeline_data(
|
||||
input: PipelineData,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
// If the table function is in the declarations, then we can use it
|
||||
// to create the table value that will be printed in the terminal
|
||||
|
||||
let config = stack.get_config().unwrap_or_default();
|
||||
|
||||
let stdout = std::io::stdout();
|
||||
|
||||
if let PipelineData::ExternalStream {
|
||||
stdout: stream,
|
||||
exit_code,
|
||||
..
|
||||
} = input
|
||||
{
|
||||
if let Some(stream) = stream {
|
||||
for s in stream {
|
||||
let _ = stdout.lock().write_all(s?.as_binary()?);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let _: Vec<_> = exit_code.into_iter().collect();
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match engine_state.find_decl("table".as_bytes()) {
|
||||
Some(decl_id) => {
|
||||
let table = engine_state.get_decl(decl_id).run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
input,
|
||||
)?;
|
||||
|
||||
for item in table {
|
||||
let stdout = std::io::stdout();
|
||||
|
||||
if let Value::Error { error } = item {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", &config);
|
||||
out.push('\n');
|
||||
|
||||
match stdout.lock().write_all(out.as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(err) => eprintln!("{}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for item in input {
|
||||
let stdout = std::io::stdout();
|
||||
|
||||
if let Value::Error { error } = item {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", &config);
|
||||
out.push('\n');
|
||||
|
||||
match stdout.lock().write_all(out.as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(err) => eprintln!("{}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 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.
|
||||
pub fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
||||
// Some helper functions
|
||||
fn get_surround_char(s: &str) -> Option<char> {
|
||||
if s.contains('"') {
|
||||
if s.contains('\'') {
|
||||
None
|
||||
} else {
|
||||
Some('\'')
|
||||
}
|
||||
} else {
|
||||
Some('\'')
|
||||
}
|
||||
}
|
||||
|
||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::LabeledError(
|
||||
format!("Environment variable was not captured: {}", env_str),
|
||||
msg.into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn put_env_to_fake_file(
|
||||
name: &str,
|
||||
val: &str,
|
||||
fake_env_file: &mut String,
|
||||
engine_state: &EngineState,
|
||||
) {
|
||||
let (c_name, c_val) =
|
||||
if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) {
|
||||
(cn, cv)
|
||||
} else {
|
||||
// environment variable with its name or value containing both ' and " is ignored
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&format!("{}={}", name, val),
|
||||
"Name or value should not contain both ' and \" at the same time.",
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
fake_env_file.push(c_name);
|
||||
fake_env_file.push_str(name);
|
||||
fake_env_file.push(c_name);
|
||||
fake_env_file.push('=');
|
||||
fake_env_file.push(c_val);
|
||||
fake_env_file.push_str(val);
|
||||
fake_env_file.push(c_val);
|
||||
fake_env_file.push('\n');
|
||||
}
|
||||
|
||||
let mut fake_env_file = String::new();
|
||||
|
||||
// Make sure we always have PWD
|
||||
if std::env::var("PWD").is_err() {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
put_env_to_fake_file(
|
||||
"PWD",
|
||||
&cwd.to_string_lossy(),
|
||||
&mut fake_env_file,
|
||||
engine_state,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::LabeledError(
|
||||
"Current directory not found".to_string(),
|
||||
format!("Retrieving current directory failed: {:?}", e),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write all the env vars into a fake file
|
||||
for (name, val) in std::env::vars() {
|
||||
put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state);
|
||||
}
|
||||
|
||||
// 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 = trim_quotes(bytes);
|
||||
String::from_utf8_lossy(bytes).to_string()
|
||||
} 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 = trim_quotes(bytes);
|
||||
|
||||
Value::String {
|
||||
val: String::from_utf8_lossy(bytes).to_string(),
|
||||
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.env_vars.insert(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 {
|
||||
report_error(&working_set, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let cwd = match nu_engine::env::current_dir_str(engine_state, stack) {
|
||||
Ok(p) => PathBuf::from(p),
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
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 {
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::Int {
|
||||
val: 0,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::Int {
|
||||
val: 0,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {
|
||||
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) => {
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::Int {
|
||||
val: 1,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn seems_like_number(bytes: &[u8]) -> bool {
|
||||
if bytes.is_empty() {
|
||||
false
|
||||
} else {
|
||||
let b = bytes[0];
|
||||
|
||||
b == b'0'
|
||||
|| b == b'1'
|
||||
|| b == b'2'
|
||||
|| b == b'3'
|
||||
|| b == b'4'
|
||||
|| b == b'5'
|
||||
|| b == b'6'
|
||||
|| b == b'7'
|
||||
|| b == b'8'
|
||||
|| b == b'9'
|
||||
|| b == b'('
|
||||
|| b == b'{'
|
||||
|| b == b'['
|
||||
|| b == b'$'
|
||||
|| b == b'"'
|
||||
|| b == b'\''
|
||||
|| b == b'-'
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds externals that have names that look like math expressions
|
||||
pub fn external_exceptions(engine_state: &EngineState, stack: &Stack) -> Vec<Vec<u8>> {
|
||||
let mut executables = vec![];
|
||||
|
||||
if let Some(path) = stack.get_env_var(engine_state, "PATH") {
|
||||
match path {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
let path = val.as_string();
|
||||
|
||||
if let Ok(path) = path {
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if is_executable::is_executable(&item.path()) {
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
if seems_like_number(name.as_bytes()) {
|
||||
let name = name.as_bytes().to_vec();
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = item.path().file_stem() {
|
||||
let name = name.to_string_lossy();
|
||||
if seems_like_number(name.as_bytes()) {
|
||||
let name = name.as_bytes().to_vec();
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::String { val, .. } => {
|
||||
for path in std::env::split_paths(&val) {
|
||||
let path = path.to_string_lossy().to_string();
|
||||
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if is_executable::is_executable(&item.path()) {
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
if seems_like_number(name.as_bytes()) {
|
||||
let name = name.as_bytes().to_vec();
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
if let Some(name) = item.path().file_stem() {
|
||||
let name = name.to_string_lossy();
|
||||
if seems_like_number(name.as_bytes()) {
|
||||
let name = name.as_bytes().to_vec();
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
executables
|
||||
}
|
||||
|
||||
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 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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
||||
}
|
13
crates/nu-color-config/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "nu-color-config"
|
||||
version = "0.60.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.60.0" }
|
||||
nu-ansi-term = "0.45.0"
|
||||
|
||||
nu-json = { path = "../nu-json", version = "0.60.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.60.0" }
|
||||
|
||||
serde = { version="1.0.123", features=["derive"] }
|
411
crates/nu-color-config/src/color_config.rs
Normal file
@ -0,0 +1,411 @@
|
||||
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(),
|
||||
|
||||
_ => 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
@ -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
@ -0,0 +1,103 @@
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, 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)?,
|
||||
)))
|
||||
}
|
||||
}
|
40
crates/nu-color-config/src/shape_color.rs
Normal file
@ -0,0 +1,40 @@
|
||||
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_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(),
|
||||
},
|
||||
}
|
||||
}
|
@ -1,122 +1,108 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.42.0"
|
||||
version = "0.60.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-data = { version = "0.42.0", path="../nu-data" }
|
||||
nu-engine = { version = "0.42.0", path="../nu-engine" }
|
||||
nu-errors = { version = "0.42.0", path="../nu-errors" }
|
||||
nu-json = { version = "0.42.0", path="../nu-json" }
|
||||
nu-path = { version = "0.42.0", path="../nu-path" }
|
||||
nu-parser = { version = "0.42.0", path="../nu-parser" }
|
||||
nu-plugin = { version = "0.42.0", path="../nu-plugin" }
|
||||
nu-protocol = { version = "0.42.0", path="../nu-protocol" }
|
||||
nu-serde = { version = "0.42.0", path="../nu-serde" }
|
||||
nu-source = { version = "0.42.0", path="../nu-source" }
|
||||
nu-stream = { version = "0.42.0", path="../nu-stream" }
|
||||
nu-table = { version = "0.42.0", path="../nu-table" }
|
||||
nu-test-support = { version = "0.42.0", path="../nu-test-support" }
|
||||
nu-value-ext = { version = "0.42.0", path="../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.42.0", path="../nu-ansi-term" }
|
||||
nu-pretty-hex = { version = "0.42.0", path="../nu-pretty-hex" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.60.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.60.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.60.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.60.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.60.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.60.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.60.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.60.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.60.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.60.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.60.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.60.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.60.0" }
|
||||
nu-ansi-term = "0.45.0"
|
||||
|
||||
url = "2.2.1"
|
||||
mime = "0.3.16"
|
||||
heck = "0.4.0"
|
||||
# Potential dependencies for extras
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = { version = "0.3.0", features = ["serde"] }
|
||||
bytesize = "1.1.0"
|
||||
calamine = "0.18.0"
|
||||
chrono = { version="0.4.19", features=["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
crossterm = { version="0.19.0", optional=true }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono-humanize = "0.2.1"
|
||||
chrono-tz = "0.6.0"
|
||||
crossterm = "0.23.0"
|
||||
csv = "1.1.3"
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
derive-new = "0.5.8"
|
||||
dirs-next = "2.0.0"
|
||||
dialoguer = "0.9.0"
|
||||
digest = "0.10.0"
|
||||
dtparse = "1.2.0"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.28"
|
||||
encoding_rs = "0.8.30"
|
||||
filesize = "0.2.0"
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
glob = "0.3.0"
|
||||
fs_extra = "1.2.0"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
indexmap = { version="1.7", features=["serde-1"] }
|
||||
Inflector = "0.11"
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
md-5 = "0.9.1"
|
||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
meval = "0.2.0"
|
||||
num-bigint = { version="0.4.3", features=["serde"] }
|
||||
num-format = { version="0.4.0", features=["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
parking_lot = "0.11.1"
|
||||
mime = "0.3.16"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
pathdiff = "0.2.1"
|
||||
quick-xml = "0.22"
|
||||
rand = "0.8"
|
||||
regex = "1.4.3"
|
||||
reqwest = {version = "0.11", optional = true }
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version="9.0.0", optional=true }
|
||||
rust-embed = "6.3.0"
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sysinfo = { version = "0.21.2", optional = true }
|
||||
thiserror = "1.0.26"
|
||||
term = { version="0.7.0", optional=true }
|
||||
term_size = "0.3.2"
|
||||
sha2 = "0.10.0"
|
||||
shadow-rs = "0.8.1"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.23.5"
|
||||
terminal_size = "0.1.17"
|
||||
thiserror = "1.0.29"
|
||||
titlecase = "1.1.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
toml = "0.5.8"
|
||||
trash = { version = "2.0.2", optional = true }
|
||||
unicode-segmentation = "1.8"
|
||||
uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
|
||||
which = { version="4.1.0", optional=true }
|
||||
zip = { version="0.5.9", optional=true }
|
||||
digest = "0.9.0"
|
||||
|
||||
[dependencies.polars]
|
||||
version = "0.17.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
features = ["docs", "zip_with", "csv-file", "temporal", "performant", "pretty_fmt", "dtype-slim", "parquet", "json", "random", "pivot", "strings", "is_in", "cum_agg", "rolling_window"]
|
||||
unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
which = { version = "4.2.2", optional = true }
|
||||
reedline = "0.3.0"
|
||||
zip = { version="0.5.9", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.11.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
# (currently on nightly behind -Zfeatures=itarget):
|
||||
# https://github.com/rust-lang/cargo/issues/7914
|
||||
# [target.'cfg(not(windows))'.dependencies]
|
||||
# num-format = { version = "0.4", features = ["with-system-locale"] }
|
||||
[dependencies.polars]
|
||||
version = "0.20.0"
|
||||
optional = true
|
||||
features = [
|
||||
"default", "parquet", "json", "serde", "object",
|
||||
"checked_arithmetic", "strings", "cum_agg", "is_in",
|
||||
"rolling_window", "strings", "rows", "random",
|
||||
"dtype-datetime"
|
||||
]
|
||||
|
||||
[features]
|
||||
trash-support = ["trash"]
|
||||
plugin = ["nu-parser/plugin"]
|
||||
dataframe = ["polars", "num"]
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.8.1"
|
||||
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
|
||||
[features]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
dataframe = ["nu-protocol/dataframe", "polars"]
|
||||
fetch = ["reqwest", "tokio"]
|
||||
post = ["reqwest", "tokio"]
|
||||
sys = ["sysinfo"]
|
||||
ps = ["sysinfo"]
|
||||
|
@ -1,7 +0,0 @@
|
||||
# nu-command
|
||||
|
||||
The Nu command crate contains the full set of internal commands, that is, the commands that can be form the set of built-in commands in a Nushell engine.
|
||||
|
||||
The default set of commands that Nushell ships with can be found in the [default context](src/default_context.rs).
|
||||
|
||||
The commands themselves live in the [commands module](src/commands/).
|
@ -1,10 +0,0 @@
|
||||
use nu_protocol::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogLevel {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LogItem {
|
||||
level: LogLevel,
|
||||
value: Value,
|
||||
}
|
@ -1,725 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use nu_engine::{evaluate_baseline_expr, BufCodecReader};
|
||||
use nu_engine::{MaybeTextCodec, StringOrBinary};
|
||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
|
||||
#[allow(unused)]
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::{borrow::Cow, io::BufReader};
|
||||
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use which::which_in;
|
||||
|
||||
pub(crate) fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
context.sync_path_to_env();
|
||||
if !context.host().lock().is_external_cmd(&command.name) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Command not found",
|
||||
format!("command {} not found", &command.name),
|
||||
&command.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, external_redirection)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn trim_enclosing_quotes(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
|
||||
match (chars.next(), chars.next_back()) {
|
||||
(Some('"'), Some('"')) => chars.collect(),
|
||||
(Some('\''), Some('\'')) => chars.collect(),
|
||||
_ => input.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager().path();
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, context)?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
// what value we would put in its place.
|
||||
if value.value.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
match &value.value {
|
||||
UntaggedValue::Table(table) => {
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args.push((
|
||||
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||
is_literal,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not convert to positional arguments",
|
||||
"could not convert to positional arguments",
|
||||
value.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
//let trimmed_value_string = trim_quotes(&trimmed_value_string);
|
||||
command_args.push((trimmed_value_string, is_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|(arg, _is_literal)| {
|
||||
let arg = nu_path::expand_tilde(arg).to_string_lossy().to_string();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if !_is_literal {
|
||||
arg
|
||||
} else {
|
||||
trim_enclosing_quotes(&arg)
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(
|
||||
&command,
|
||||
&path,
|
||||
&process_args[..],
|
||||
input,
|
||||
external_redirection,
|
||||
&context.scope,
|
||||
)
|
||||
}
|
||||
|
||||
/// Spawn a direct exe
|
||||
#[allow(unused)]
|
||||
fn spawn_exe(full_path: PathBuf, args: &[String]) -> Command {
|
||||
let mut process = Command::new(full_path);
|
||||
for arg in args {
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
}
|
||||
|
||||
/// Spawn a cmd command with `cmd /c args...`
|
||||
fn spawn_cmd_command(command: &ExternalCommand, args: &[String]) -> Command {
|
||||
let mut process = Command::new("cmd");
|
||||
process.arg("/c");
|
||||
process.arg(&command.name);
|
||||
for arg in args {
|
||||
// Clean the args before we use them:
|
||||
// https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe
|
||||
// cmd.exe needs to have a caret to escape a pipe
|
||||
let arg = arg.replace("|", "^|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
}
|
||||
|
||||
fn has_unsafe_shell_characters(arg: &str) -> bool {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"[^\w@%+=:,./-]").expect("regex to be valid");
|
||||
}
|
||||
|
||||
RE.is_match(arg)
|
||||
}
|
||||
|
||||
fn shell_arg_escape(arg: &str) -> String {
|
||||
match arg {
|
||||
"" => String::from("''"),
|
||||
s if !has_unsafe_shell_characters(s) => String::from(s),
|
||||
_ => {
|
||||
let single_quotes_escaped = arg.split('\'').join("'\"'\"'");
|
||||
format!("'{}'", single_quotes_escaped)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a sh command with `sh -c args...`
|
||||
fn spawn_sh_command(command: &ExternalCommand, args: &[String]) -> Command {
|
||||
let joined_and_escaped_arguments = args.iter().map(|arg| shell_arg_escape(arg)).join(" ");
|
||||
let cmd_with_args = vec![command.name.clone(), joined_and_escaped_arguments].join(" ");
|
||||
let mut process = Command::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
|
||||
/// a function to spawn any external command
|
||||
#[allow(unused)] // for minimal builds cwd is unused
|
||||
fn spawn_any(command: &ExternalCommand, args: &[String], cwd: &str) -> Command {
|
||||
// resolve the executable name if it is spawnable directly
|
||||
#[cfg(feature = "which")]
|
||||
// TODO add more available paths to `env::var_os("PATH")`?
|
||||
if let Result::Ok(full_path) = which_in(&command.name, env::var_os("PATH"), cwd) {
|
||||
if let Some(extension) = full_path.extension() {
|
||||
#[cfg(windows)]
|
||||
if extension.eq_ignore_ascii_case("exe") {
|
||||
// if exe spawn it directly
|
||||
return spawn_exe(full_path, args);
|
||||
} else {
|
||||
// TODO implement special care for various executable types such as .bat, .ps1, .cmd, etc
|
||||
// https://github.com/mklement0/Native/blob/e0e0b8785cad39a73053e35084d1f60d87fbac58/Native.psm1#L749
|
||||
// otherwise shell out to cmd
|
||||
return spawn_cmd_command(command, args);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
if !["sh", "bash"]
|
||||
.iter()
|
||||
.any(|ext| extension.eq_ignore_ascii_case(ext))
|
||||
{
|
||||
// if exe spawn it directly
|
||||
return spawn_exe(full_path, args);
|
||||
} else {
|
||||
// otherwise shell out to sh
|
||||
return spawn_sh_command(command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
// in all the other cases shell out
|
||||
if cfg!(windows) {
|
||||
spawn_cmd_command(command, args)
|
||||
} else {
|
||||
// TODO what happens if that os doesn't support spawning sh?
|
||||
spawn_sh_command(command, args)
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
command: &ExternalCommand,
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
let mut process = spawn_any(&command, args, path);
|
||||
process.current_dir(path);
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.get_env_vars());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
match external_redirection {
|
||||
ExternalRedirection::Stdout => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
}
|
||||
ExternalRedirection::Stderr => {
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
ExternalRedirection::StdoutAndStderr => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
if !input.is_empty() {
|
||||
process.stdin(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||
}
|
||||
|
||||
trace!(target: "nu::run::external", "built command {:?}", process);
|
||||
|
||||
// TODO Switch to async_std::process once it's stabilized
|
||||
match process.spawn() {
|
||||
Ok(mut child) => {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
|
||||
let mut stdin = child.stdin.take();
|
||||
|
||||
let stdin_write_tx = tx.clone();
|
||||
let stdout_read_tx = tx;
|
||||
let stdin_name_tag = command.name_tag.clone();
|
||||
let stdout_name_tag = command.name_tag;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !input.is_empty() {
|
||||
let mut stdin_write = stdin
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
|
||||
for value in input {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
if stdin_write.write(s.as_bytes()).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if stdin_write.write(b).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
println!("Unsupported: {:?}", unsupported);
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
"Received unexpected type from pipeline ({})",
|
||||
unsupported.type_name()
|
||||
),
|
||||
format!(
|
||||
"expected a string, got {} as input",
|
||||
unsupported.type_name()
|
||||
),
|
||||
stdin_name_tag.clone(),
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if external_redirection == ExternalRedirection::Stdout
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||
stdout
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stdout for external command",
|
||||
"can't redirect stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stdout);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_read = BufReader::new(stdout);
|
||||
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(
|
||||
s.clone(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(b) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(
|
||||
b.into_iter().collect(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if external_redirection == ExternalRedirection::Stderr
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stderr);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_reader = BufReader::new(stderr);
|
||||
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
// than what other shells will do.
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = nu_data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"External command failed",
|
||||
"command failed",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::nothing(),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ChannelReceiver::new(rx);
|
||||
Ok(stream.into_input_stream())
|
||||
}
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
"failed to spawn",
|
||||
&command.name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelReceiver {
|
||||
rx: Arc<Mutex<mpsc::Receiver<Result<Value, ShellError>>>>,
|
||||
}
|
||||
|
||||
impl ChannelReceiver {
|
||||
pub fn new(rx: mpsc::Receiver<Result<Value, ShellError>>) -> Self {
|
||||
Self {
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ChannelReceiver {
|
||||
type Item = Result<Value, ShellError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rx = self.rx.lock();
|
||||
match rx.recv() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
(argument.starts_with('"') && argument.ends_with('"'))
|
||||
|| (argument.starts_with('\'') && argument.ends_with('\''))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_double_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||
// allocate new string only if required
|
||||
if argument.contains('"') {
|
||||
Cow::Owned(argument.replace('"', r#"\""#))
|
||||
} else {
|
||||
Cow::Borrowed(argument)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = argument.len();
|
||||
|
||||
Some(&argument[1..size - 1])
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
let mut original_paths = vec![];
|
||||
|
||||
if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) {
|
||||
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
original_paths
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{add_double_quotes, argument_is_quoted, escape_double_quotes, remove_quotes};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
// fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next() {
|
||||
// Ok(val) => {
|
||||
// if let Some(val) = val {
|
||||
// val.raw_value()
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// Err(_) => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
fn non_existent_run() {
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx = EvaluationContext::basic();
|
||||
|
||||
assert!(run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout).is_err());
|
||||
}
|
||||
|
||||
// fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = crate::cli::EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// ?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
||||
// match read(stream.into()) {
|
||||
// Some(Value {
|
||||
// value: UntaggedValue::Error(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
// None | _ => panic!("Command didn't fail."),
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn identifies_command_failed() -> Result<(), ShellError> {
|
||||
// block_on(failure_run())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
#[test]
|
||||
fn identifies_command_not_found() {
|
||||
non_existent_run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_escape_double_quotes() {
|
||||
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_quotes_from_argument_to_be_passed_in() {
|
||||
assert!(!argument_is_quoted(""));
|
||||
|
||||
assert!(!argument_is_quoted("'"));
|
||||
assert!(!argument_is_quoted("'a"));
|
||||
assert!(!argument_is_quoted("a"));
|
||||
assert!(!argument_is_quoted("a'"));
|
||||
assert!(argument_is_quoted("''"));
|
||||
|
||||
assert!(!argument_is_quoted(r#"""#));
|
||||
assert!(!argument_is_quoted(r#""a"#));
|
||||
assert!(!argument_is_quoted(r#"a"#));
|
||||
assert!(!argument_is_quoted(r#"a""#));
|
||||
assert!(argument_is_quoted(r#""""#));
|
||||
|
||||
assert!(!argument_is_quoted("'andrés"));
|
||||
assert!(!argument_is_quoted("andrés'"));
|
||||
assert!(!argument_is_quoted(r#""andrés"#));
|
||||
assert!(!argument_is_quoted(r#"andrés""#));
|
||||
assert!(argument_is_quoted("'andrés'"));
|
||||
assert!(argument_is_quoted(r#""andrés""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(remove_quotes(""), None);
|
||||
|
||||
assert_eq!(remove_quotes("'"), None);
|
||||
assert_eq!(remove_quotes("'a"), None);
|
||||
assert_eq!(remove_quotes("a"), None);
|
||||
assert_eq!(remove_quotes("a'"), None);
|
||||
assert_eq!(remove_quotes("''"), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes(r#"""#), None);
|
||||
assert_eq!(remove_quotes(r#""a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a""#), None);
|
||||
assert_eq!(remove_quotes(r#""""#), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes("'andrés"), None);
|
||||
assert_eq!(remove_quotes("andrés'"), None);
|
||||
assert_eq!(remove_quotes(r#""andrés"#), None);
|
||||
assert_eq!(remove_quotes(r#"andrés""#), None);
|
||||
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
||||
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub(crate) mod external;
|
@ -1,46 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chart;
|
||||
|
||||
impl WholeStreamCommand for Chart {
|
||||
fn name(&self) -> &str {
|
||||
"chart"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("chart")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Displays charts."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
if args.scope().get_command("chart bar").is_none() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"nu_plugin_chart not installed.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ActionStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_full_help(&Chart, args.scope())).into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Chart;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Chart {})
|
||||
}
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Histogram;
|
||||
|
||||
impl WholeStreamCommand for Histogram {
|
||||
fn name(&self) -> &str {
|
||||
"histogram"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("histogram")
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"Use data at the column path given as valuator",
|
||||
None,
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::ColumnPath,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new table with a histogram based on the column name passed in."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
histogram(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a histogram for the types of files",
|
||||
example: "ls | histogram type",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Get a histogram for the types of files, with frequency column named percentage",
|
||||
example: "ls | histogram type percentage",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get a histogram for a list of numbers",
|
||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let mut columns = args.rest::<ColumnPath>(0)?;
|
||||
let evaluate_with = args.get_flag::<ColumnPath>("use")?.map(evaluator);
|
||||
let values: Vec<Value> = args.input.collect();
|
||||
|
||||
let column_grouper = if !columns.is_empty() {
|
||||
columns
|
||||
.remove(0)
|
||||
.split_last()
|
||||
.map(|(key, _)| key.as_string().tagged(&name))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let frequency_column_name = if columns.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else if let Some((key, _)) = columns[0].split_last() {
|
||||
key.as_string()
|
||||
} else {
|
||||
"frequency".to_string()
|
||||
};
|
||||
|
||||
let column = if let Some(ref column) = column_grouper {
|
||||
column.clone()
|
||||
} else {
|
||||
"value".to_string().tagged(&name)
|
||||
};
|
||||
|
||||
let results = nu_data::utils::report(
|
||||
&UntaggedValue::table(&values).into_value(&name),
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: &None,
|
||||
eval: &evaluate_with,
|
||||
reduction: &nu_data::utils::Reduction::Count,
|
||||
},
|
||||
&name,
|
||||
)?;
|
||||
|
||||
let labels = results.labels.y.clone();
|
||||
let mut idx = 0;
|
||||
|
||||
Ok(results
|
||||
.data
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.zip(
|
||||
results
|
||||
.percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
)
|
||||
.map(move |(counts, percentages)| {
|
||||
let percentage = percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| {
|
||||
UntaggedValue::decimal_from_float(0.0, name.span).into_value(&name)
|
||||
});
|
||||
let value = counts
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| UntaggedValue::int(0).into_value(&name));
|
||||
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let column_value = labels
|
||||
.get(idx)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Unable to load group labels",
|
||||
"unable to load group labels",
|
||||
&name,
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
|
||||
fact.insert_value(&column.item, column_value);
|
||||
fact.insert_untagged("count", value);
|
||||
|
||||
let fmt_percentage = format!(
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::conversions::into::string::action(
|
||||
&percentage,
|
||||
&name,
|
||||
Some(2),
|
||||
true
|
||||
)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
|
||||
|
||||
let string = "*".repeat(percentage.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize);
|
||||
|
||||
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
|
||||
|
||||
idx += 1;
|
||||
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
})
|
||||
.into_action_stream())
|
||||
}
|
||||
|
||||
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn splitter(
|
||||
by: Option<Tagged<String>>,
|
||||
) -> Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send> {
|
||||
match by {
|
||||
Some(column) => Box::new(move |_, row: &Value| {
|
||||
let key = &column;
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => nu_value_ext::as_string(&key),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
}),
|
||||
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(row)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Histogram;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Histogram {})
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod chart;
|
||||
mod histogram;
|
||||
|
||||
pub use chart::Chart;
|
||||
pub use histogram::Histogram;
|
@ -1,53 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config clear"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config clear")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clear(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Clear the config (be careful!)",
|
||||
example: "config clear",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
|
||||
let result = if let Some(global_cfg) = &mut args.configs().lock().global_config {
|
||||
global_cfg.vars.clear();
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
let value = UntaggedValue::Row(global_cfg.vars.clone().into()).into_value(name);
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::CommandArgs;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"config"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
if let Some(global_cfg) = &args.configs().lock().global_config {
|
||||
let result = global_cfg.vars.clone();
|
||||
let value = UntaggedValue::Row(result.into()).into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config get").required(
|
||||
"get",
|
||||
SyntaxShape::ColumnPath,
|
||||
"value to get from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the current startup commands",
|
||||
example: "config get startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
|
||||
let column_path = args.req(0)?;
|
||||
|
||||
let result = if let Some(global_cfg) = &ctx.configs().lock().global_config {
|
||||
let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
|
||||
let value = crate::commands::filters::get::get_column_path(&column_path, &result)?;
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => OutputStream::from_stream(list.into_iter()),
|
||||
x => OutputStream::one(x),
|
||||
})
|
||||
} else {
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
pub mod clear;
|
||||
pub mod command;
|
||||
pub mod get;
|
||||
pub mod path;
|
||||
pub mod remove;
|
||||
pub mod set;
|
||||
pub mod set_into;
|
||||
|
||||
pub use clear::SubCommand as ConfigClear;
|
||||
pub use command::Command as Config;
|
||||
pub use get::SubCommand as ConfigGet;
|
||||
pub use path::SubCommand as ConfigPath;
|
||||
pub use remove::SubCommand as ConfigRemove;
|
||||
pub use set::SubCommand as ConfigSet;
|
||||
pub use set_into::SubCommand as ConfigSetInto;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
|
||||
pub fn err_no_global_cfg_present() -> ShellError {
|
||||
ShellError::untagged_runtime_error("No global config found!")
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config path"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
path(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the path to the current config file",
|
||||
example: "config path",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
if let Some(global_cfg) = &mut args.configs().lock().global_config {
|
||||
let value = UntaggedValue::Primitive(Primitive::FilePath(global_cfg.file_path.clone()))
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config remove"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config remove").required(
|
||||
"remove",
|
||||
SyntaxShape::Any,
|
||||
"remove a value from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Remove the startup commands",
|
||||
example: "config remove startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
let remove: Tagged<String> = args.req(0)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
|
||||
if global_cfg.vars.contains_key(&key) {
|
||||
global_cfg.vars.swap_remove(&key);
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
let value: Value = UntaggedValue::row(global_cfg.vars.clone()).into_value(remove.tag);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
"key",
|
||||
remove.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
}
|