mirror of
https://github.com/nushell/nushell.git
synced 2025-07-02 23:51:49 +02:00
Compare commits
705 Commits
Author | SHA1 | Date | |
---|---|---|---|
a1b7261121 | |||
5a4b6f0f7f | |||
78b227d1ef | |||
07598c9620 | |||
7413ef2824 | |||
0cc735a7b2 | |||
d00038eb4b | |||
47af701380 | |||
24b4ac692e | |||
fb72da0e82 | |||
d339902dc6 | |||
393f424f1c | |||
c8f54476c9 | |||
d42c2b2dbc | |||
ed64a44b82 | |||
1855dfb656 | |||
91c01bf6b3 | |||
29256b161c | |||
32f098d91d | |||
06996d8c7f | |||
2306ef3063 | |||
f82a1d8e4e | |||
f0e0ab35fc | |||
3b20d6890c | |||
6eb00f6c60 | |||
4ecec59224 | |||
bf3bb66c3e | |||
6b3236715b | |||
cbedc8403f | |||
1d68c48a92 | |||
fff4de5c44 | |||
45d33e70db | |||
9b35d59023 | |||
71611dec4f | |||
a122e55129 | |||
6cedc05384 | |||
8efbb48cb0 | |||
0bfa769b7d | |||
5afc49250f | |||
efb81a1277 | |||
4a8124ad1e | |||
8ddebcb932 | |||
b2d7427d2d | |||
b4400c4896 | |||
5a8d4c628f | |||
ebdb7ac2d8 | |||
a33a7960bb | |||
3603610026 | |||
017151dff1 | |||
e892aad3f6 | |||
ad90b6e5f3 | |||
4da7bbbb59 | |||
b9808c8598 | |||
36036d9308 | |||
99c0a2575f | |||
487789b45b | |||
b3d6896977 | |||
8ee52b6ee1 | |||
46dba8853a | |||
eb4d19fb9c | |||
c3678764b4 | |||
9bb2c8faf5 | |||
74dcac3b0d | |||
6d51e34d0a | |||
de76c7a57d | |||
0e23400510 | |||
d0a83fec69 | |||
57510f2fd2 | |||
58b96fdede | |||
9e3d6c3bfd | |||
4a955d7e76 | |||
637283ffad | |||
5afbfb5c2c | |||
d128c0e02b | |||
60e6ea5abd | |||
415607706f | |||
f2b977b9c5 | |||
35e8420780 | |||
1c5846e1fb | |||
5ec6edb9c5 | |||
5d8bedfbe4 | |||
0477493734 | |||
e6b196c141 | |||
49960beb35 | |||
1b677f167e | |||
d881481758 | |||
a3ea0c304a | |||
4fda6d7eaa | |||
771e24913d | |||
aded2c1937 | |||
e54b867e8e | |||
c12b4b4af7 | |||
87ddba0193 | |||
a29b61bd4f | |||
7c6ea81dd4 | |||
d06ebb1686 | |||
d93953a56f | |||
74283c3ebc | |||
8a030f3bfc | |||
5518ffd248 | |||
bcdb9bf5b4 | |||
3509bde1a9 | |||
add20873d0 | |||
427db0d101 | |||
7bac0b417f | |||
56efbd7de9 | |||
91282d4404 | |||
398976e43e | |||
f723bc6989 | |||
22142bd4ae | |||
caf1432dc7 | |||
65c90d5b45 | |||
50ca77437d | |||
71b4949843 | |||
54a18991ab | |||
1fcb98289a | |||
01e5ba01f6 | |||
1134c2f16c | |||
d18cf19a3f | |||
2ec2028637 | |||
b84a01cb1d | |||
ca4d8008d4 | |||
a256f6d0d1 | |||
0aa6954f33 | |||
68d98fcf24 | |||
87086262f3 | |||
3fab427383 | |||
61fa826159 | |||
0b9fc4ff3a | |||
1817d5e01e | |||
0ca0f8ec17 | |||
6be5631477 | |||
678e942bd8 | |||
83ddf0ebe2 | |||
eaea00366b | |||
0788fe5e72 | |||
b2257a5ca3 | |||
3bf5999ef4 | |||
8a85299575 | |||
3db0aed9f7 | |||
09276db2a5 | |||
0e496f900d | |||
1ed645c6c2 | |||
bc6948dc89 | |||
e9c17daecd | |||
53beba7acc | |||
ed0fce9aa6 | |||
995603b08c | |||
a49e5b30ff | |||
97e7d550c8 | |||
bc54930bc6 | |||
0d6e43097d | |||
393717dbb4 | |||
da8cb14f8b | |||
9f01cf333c | |||
d391e912ff | |||
8b185a4008 | |||
90b65018b6 | |||
7ec5f2f2eb | |||
332f1192a6 | |||
944cad35bf | |||
86ae27b0c1 | |||
d9a888528a | |||
05f1b41275 | |||
5b03bca138 | |||
d409171ba8 | |||
0567407f85 | |||
6872d2ac2a | |||
ad4450f9e8 | |||
d8478ca690 | |||
aab31833a2 | |||
c0648a83be | |||
744a28b31d | |||
b4b68afa17 | |||
dd22647fcd | |||
f66136bc86 | |||
8cf9bc9993 | |||
d0aa69bfcb | |||
4a1d12462f | |||
8d5fbc6fcb | |||
85bfdba1e2 | |||
546c753d1e | |||
2c3aade057 | |||
be52f7fb07 | |||
ec5396a352 | |||
403bf1a734 | |||
05ff7a9925 | |||
5c2a767987 | |||
35798ce0cc | |||
47d6a66fbf | |||
616f065324 | |||
1e39a1a7a3 | |||
66ad83c15c | |||
c48e9cdf5b | |||
6a274b860a | |||
2f8a52d256 | |||
0f4a073eaf | |||
626410b2aa | |||
a193b85123 | |||
0f40c44ed2 | |||
cd6f86052d | |||
b9858ea8f8 | |||
cb1eefd24a | |||
a1840e9d20 | |||
b01cbf82c3 | |||
e89c796b41 | |||
758351c732 | |||
77d33766f1 | |||
b0be6c3013 | |||
93e5d8edc9 | |||
9c6bfc0be9 | |||
77e73cef66 | |||
7d963776a0 | |||
ecc153cbef | |||
d1309a36b2 | |||
1d3f6105f5 | |||
e36a2947b9 | |||
64f50a179e | |||
f9cf1d943c | |||
10a42de64f | |||
c66bd5e809 | |||
3f224db990 | |||
491a9c019c | |||
77e9f8d7df | |||
14bf0b000e | |||
0ca49091c0 | |||
bb8949f2b2 | |||
ef7fbf4bf9 | |||
eb2e2e6370 | |||
a8eef9af33 | |||
400a9d3b1e | |||
7095d8994e | |||
2d41613039 | |||
1552eb921a | |||
0ac3f7a1c8 | |||
bddb63ccb5 | |||
7625aed200 | |||
19beafa865 | |||
8543b0789d | |||
bdaa01165e | |||
b2a557d4ed | |||
0903a891e4 | |||
1b2916988e | |||
d74a260883 | |||
31d9c0889c | |||
222c0f11c3 | |||
106ca65c58 | |||
4c97b3dd28 | |||
e672689a76 | |||
2579a827fc | |||
8c487edf62 | |||
0b97f52a8b | |||
21b84a6d65 | |||
d3be5ec750 | |||
4b16406050 | |||
b0ce602e4b | |||
aa876ce24f | |||
71fdf717a8 | |||
4de0347fdc | |||
f34ac9be62 | |||
12652f897a | |||
24ee381fea | |||
494a07f6f3 | |||
61455b457d | |||
57ce6a7c66 | |||
0bd4d27e8d | |||
1701303279 | |||
fd09609b44 | |||
c7583ecdb7 | |||
4eec4a27c7 | |||
86faf753bd | |||
79d0735864 | |||
808e523adc | |||
a52386e837 | |||
c26d91fb61 | |||
0a5f8f05da | |||
a13946e3ef | |||
2e01bf9cba | |||
7e82f8d9b5 | |||
2bef85a913 | |||
e435196956 | |||
af1ab39851 | |||
baddc86d9d | |||
de6bab59bf | |||
0ff1cb1ea6 | |||
3e9bb4028a | |||
878e08cfa4 | |||
4e78f3649b | |||
ccd72fa64a | |||
03e688ea7b | |||
7e949595bd | |||
d31a51e3bc | |||
0df847da15 | |||
6af59cb0ea | |||
2ad0fcb377 | |||
f34034ae58 | |||
0e2167884d | |||
e445c41454 | |||
454d1a995c | |||
62575c9a4f | |||
4898750fc1 | |||
48b4471382 | |||
f7b8f97873 | |||
4ae1b1cc26 | |||
b7a34498e3 | |||
10fd3115c2 | |||
df60793e3b | |||
a4952bc029 | |||
f6ca62384e | |||
d6141881f2 | |||
f93033c20b | |||
33fb17776a | |||
b864a455f2 | |||
b9c78a05aa | |||
26c36e932e | |||
bd096430cb | |||
6148314dcd | |||
a7b5bd18ba | |||
e01eb42e74 | |||
c1d76bfac7 | |||
12483fac92 | |||
1a62d87a42 | |||
2ccbefe01e | |||
438062d7fc | |||
0a1af85200 | |||
5bf077d64f | |||
dec0a2517f | |||
324d625324 | |||
e22b70acff | |||
a5c604c283 | |||
644164fab3 | |||
592e677caf | |||
cc7bdebc1c | |||
27d798270b | |||
50f1e33965 | |||
ffc3727a1e | |||
b093d5d10d | |||
9e589a9d93 | |||
f8d2bff283 | |||
0a2e711351 | |||
ba5258d716 | |||
c358400351 | |||
a3f817d71b | |||
c6e2607868 | |||
a29da8c95b | |||
a09aaf3495 | |||
ffc8e752a5 | |||
6ca07b87b9 | |||
49e45915f0 | |||
96e3a3de68 | |||
2aa5c2c41f | |||
4b3e3a37a3 | |||
2492165fcb | |||
c602b5a1e8 | |||
9bbb9711e4 | |||
680405e527 | |||
44595b44c5 | |||
b27c7702f9 | |||
378a3ae05f | |||
836a56b347 | |||
b36ac8f2f8 | |||
b1e7bb899a | |||
7c285750c7 | |||
e93a8b1d32 | |||
42f0b55de0 | |||
253b223e65 | |||
85bfdca578 | |||
4dd9d0d46b | |||
fd1ac5106d | |||
b572b4ecbd | |||
585e104608 | |||
d0aefa99eb | |||
728e95c52b | |||
83087e0f9d | |||
3fb1e37473 | |||
9890966fa4 | |||
aba0fb0000 | |||
0e86ba4b63 | |||
fc23c6721a | |||
f4a129a792 | |||
8deecc0137 | |||
c7966e81c2 | |||
2659c359e9 | |||
e389e51b2b | |||
0ab6b66d8f | |||
8608d8d873 | |||
d34a2c353f | |||
150b0b6b86 | |||
d0e0701a88 | |||
28b20c5ec3 | |||
9088ef182e | |||
c4d1aa452d | |||
66e52b7cfc | |||
e761954cf0 | |||
58829e3560 | |||
62652cf8c1 | |||
4482862a40 | |||
d80ba00590 | |||
81dd4a8450 | |||
101ed629a4 | |||
95ec2fcce7 | |||
73bc3389e5 | |||
bc38a6a795 | |||
e89866bedb | |||
ca09dbbbee | |||
fa4531fd17 | |||
d17c970f8c | |||
6ca62ef131 | |||
8e84e33638 | |||
b9be416937 | |||
0a8c9b22b0 | |||
527c44ed84 | |||
8ee015a847 | |||
e8cabd16d5 | |||
88e07b5ea4 | |||
f3ee8b50e3 | |||
68ad854b0d | |||
789b2e603a | |||
66398fbf77 | |||
daeb3e5187 | |||
1fd1a3a456 | |||
30ac2d220c | |||
ade7bde813 | |||
cba3e100a0 | |||
664d8d3573 | |||
d5ce509e3a | |||
2f10d19c98 | |||
4c787af26d | |||
8136170431 | |||
007916c2c1 | |||
208ffdc1da | |||
4468dc835c | |||
90a2352337 | |||
0a9d14fcb3 | |||
7863fb1087 | |||
072d2a919d | |||
ccbdc9f6d8 | |||
0f5ea16605 | |||
1cd70d7505 | |||
b0775b3f1e | |||
2894668b3e | |||
1096e653b0 | |||
9777d755d5 | |||
58529aa0b2 | |||
64b6c02a22 | |||
0780300fb3 | |||
b9106b633b | |||
23dfaa2933 | |||
cfd2cc4970 | |||
710349768f | |||
f4bd78b86d | |||
ddb7e4e179 | |||
00601f1835 | |||
c31225fdcf | |||
16b99ed0ba | |||
3b6d340603 | |||
99aea0c71c | |||
023e244958 | |||
659d890ecf | |||
8e9ed14b89 | |||
f4bf7316fe | |||
0527f9bf0d | |||
055edd886d | |||
5e70d4121a | |||
f9b5d8bc5e | |||
2917c045fb | |||
6e6ef862c5 | |||
a7fdca05c6 | |||
ddc33dc74a | |||
a562f492e3 | |||
58f0d0b945 | |||
67d1249b2b | |||
66e5e42fb1 | |||
1f01b6438f | |||
bea7ec33c1 | |||
b5561f35b9 | |||
b796cda060 | |||
4c308b7f2f | |||
d50eb9b41b | |||
9168301369 | |||
e8d930f659 | |||
aef88aa03e | |||
ec4370069a | |||
c79ece2b21 | |||
99076af18b | |||
a0e3ad2b70 | |||
e89e734ca2 | |||
9945241b77 | |||
215ed141e7 | |||
f189ee67a1 | |||
babc7d3baf | |||
8f4807020f | |||
31e1410191 | |||
24d7227e27 | |||
c130ca1bc6 | |||
4db960c0a6 | |||
d13ce2aec9 | |||
5e957ecda6 | |||
17a265b197 | |||
3fabc8e1e6 | |||
517ef7cde7 | |||
ad14b763f9 | |||
f74694d5a3 | |||
1ea39abcff | |||
7402589775 | |||
e0cd5a714a | |||
c6eea5de6b | |||
809416e3f0 | |||
040d812343 | |||
72465e6724 | |||
ab480856a5 | |||
6ae497eedc | |||
421bc828ef | |||
ed65886ae5 | |||
8c7e2dbdf9 | |||
afb4209f10 | |||
1d8775d237 | |||
8787ec9fe8 | |||
1f810cd26a | |||
f4d7d19370 | |||
e616b2e247 | |||
2a39332d51 | |||
3c6b10c6b2 | |||
2a9226a55c | |||
3d65fd7cc4 | |||
36ddbfdc85 | |||
76292ef10c | |||
9ae2e528c5 | |||
2849e28c2b | |||
9d0e52b94d | |||
731f5f8523 | |||
9d6d43ee55 | |||
f9e99048c4 | |||
e1df8d14b4 | |||
b9419e0f36 | |||
e03c354e89 | |||
2e44e4d33c | |||
5cbaabeeab | |||
d64e381085 | |||
41306aa7e0 | |||
0bb2e47c98 | |||
ef660be285 | |||
4bac90a3b2 | |||
4182fc203e | |||
10e36c4233 | |||
bef397228f | |||
5cf47767d7 | |||
8f2d2535dc | |||
2aae8e6382 | |||
ba12b0de0d | |||
3552d03f6c | |||
8d5165c449 | |||
d8027656b5 | |||
4f57c5d56e | |||
2c5c81815a | |||
b97bfe9297 | |||
db07657e40 | |||
2d98d0fcc2 | |||
e6f6f17c6d | |||
166a927c20 | |||
625fe8866c | |||
69e7aa9fc9 | |||
a775cfe177 | |||
9e4a2ab824 | |||
24aa1f312a | |||
cde56741fb | |||
bbe694a622 | |||
7e575a718b | |||
ea9ca8b4ed | |||
0fe2884397 | |||
2982a2c963 | |||
6a43e1a64d | |||
3b5172a8fa | |||
be32aeee70 | |||
adcc74ab8d | |||
8acced56b2 | |||
f823c7cb5d | |||
26e6516626 | |||
5979e0cd0c | |||
3ba1bfc369 | |||
efa0e6eb62 | |||
159b4bd7dc | |||
2611c9525e | |||
0353eb4a12 | |||
92c4097f8d | |||
a909c60f05 | |||
56a9eab7eb | |||
7221eb7f39 | |||
b0b0482d71 | |||
49ab559992 | |||
3dd21c635a | |||
835bbb2e44 | |||
2ee2370a71 | |||
54dd65cfe1 | |||
b004aacd69 | |||
48b7b415e2 | |||
544cea95e1 | |||
8aa2632661 | |||
5419e8ae9d | |||
d4d28ab796 | |||
b8db928c58 | |||
bf45a5860e | |||
ca543fc8af | |||
57cf805e12 | |||
1ae9157985 | |||
206a6ae6c9 | |||
82ac590412 | |||
9a274128ce | |||
5664ee7bda | |||
9a56665c6b | |||
8044fb2db0 | |||
3a59ab9f14 | |||
f609a4f26a | |||
80463d12fb | |||
cef05d3553 | |||
5879b0df99 | |||
95cd9dd2b2 | |||
424d5611a5 | |||
9bff68a4f6 | |||
a9bdc655c1 | |||
9b617de6f0 | |||
771270d526 | |||
26d1307476 | |||
86707b9972 | |||
52cb865c5c | |||
3ea027a136 | |||
00469de93e | |||
9bc4e6794d | |||
429127793f | |||
75cb3fcc5f | |||
f0e87da830 | |||
c5639cd9fa | |||
95d4922e44 | |||
bdd52f0111 | |||
7bd07cb351 | |||
249afc5df4 | |||
d7af461173 | |||
b17e9f4ed0 | |||
6862734580 | |||
9e1f645428 | |||
c4818d79f3 | |||
d1a78a58cd | |||
65d0b5b9d9 | |||
614bc2a943 | |||
27b06358ea | |||
e56c01d0e2 | |||
ececca7ad2 | |||
e9cc417fd5 | |||
81a7d17b33 | |||
9382dd6d55 | |||
7aa2a57434 | |||
9b88ea5b60 | |||
8bfcea8054 | |||
f3d2be7a56 | |||
be31182969 | |||
b543063749 | |||
ce0060e6b0 | |||
35b12fe5ec | |||
6ac26094da | |||
8c6a0f68d4 | |||
f5d6672ccf | |||
db06edc5d3 | |||
568927349d | |||
4f812a7f34 | |||
38fc42d352 | |||
b4c5693ac6 | |||
79000aa5e0 | |||
2415381682 | |||
b499e7c682 | |||
d8cde2ae89 | |||
ddc00014be | |||
9ffa3e55c2 | |||
45fe3be83e | |||
dd6fe6a04a | |||
e76b38882c | |||
11bdab7e61 | |||
3d682fe957 | |||
a43e66ef92 | |||
3be7996e79 | |||
5041a4ffa3 | |||
b16b3c0b7f | |||
852ec3f9a0 | |||
dd7b7311b3 | |||
9364bad625 | |||
6fc5244439 | |||
8e1112c1dd | |||
9d1cb1bfaf | |||
216d7d035f | |||
ead6fbdf9c | |||
5b616770df | |||
23a5c5dc09 | |||
74656bf976 | |||
d8a2e0e9a3 | |||
046e46b962 | |||
ec08e4bc6d | |||
05e07ddf5c | |||
757d7479af | |||
440feaf74a | |||
22c50185b5 | |||
3a2c7900d6 | |||
fa8629300f | |||
37dc226996 | |||
4e1f94026c | |||
d27263af97 | |||
215f1af1da |
5
.githooks/pre-commit
Executable file
5
.githooks/pre-commit
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
use ../toolkit.nu fmt
|
||||
|
||||
fmt --check --verbose
|
6
.githooks/pre-push
Executable file
6
.githooks/pre-push
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
use ../toolkit.nu [fmt, clippy]
|
||||
|
||||
fmt --check --verbose
|
||||
clippy --verbose
|
11
.github/ISSUE_TEMPLATE/standard-library-bug-or-feature-report.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/standard-library-bug-or-feature-report.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
name: standard library bug or feature report
|
||||
about: Used to submit issues related to the nu standard library
|
||||
title: ''
|
||||
labels: std-library
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug or feature**
|
||||
A clear and concise description of what the bug is.
|
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
# docs
|
||||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
24
.github/pull_request_template.md
vendored
24
.github/pull_request_template.md
vendored
@ -1,16 +1,16 @@
|
||||
|
||||
# Description
|
||||
<!--
|
||||
Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.
|
||||
|
||||
_(Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.)_
|
||||
|
||||
_(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_
|
||||
Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.
|
||||
-->
|
||||
|
||||
# User-Facing Changes
|
||||
|
||||
_(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_
|
||||
<!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. -->
|
||||
|
||||
# Tests + Formatting
|
||||
|
||||
<!--
|
||||
Don't forget to add tests that cover your changes.
|
||||
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
@ -18,7 +18,15 @@ Make sure you've run and fixed any issues with these commands:
|
||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass
|
||||
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the standard library
|
||||
|
||||
> **Note**
|
||||
> from `nushell` you can also use the `toolkit` as follows
|
||||
> ```bash
|
||||
> use toolkit.nu # or use an `env_change` hook to activate it automatically
|
||||
> toolkit check pr
|
||||
> ```
|
||||
-->
|
||||
|
||||
# After Submitting
|
||||
|
||||
If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
|
||||
<!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
|
||||
|
90
.github/workflows/ci.yml
vendored
90
.github/workflows/ci.yml
vendored
@ -15,8 +15,22 @@ jobs:
|
||||
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
||||
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||
style: [default, dataframe]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: default
|
||||
flags: ""
|
||||
- style: dataframe
|
||||
flags: "--features=dataframe "
|
||||
exclude:
|
||||
# only test dataframes on Ubuntu (the fastest platform)
|
||||
- platform: windows-latest
|
||||
style: dataframe
|
||||
- platform: macos-latest
|
||||
style: dataframe
|
||||
|
||||
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
@ -26,13 +40,13 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
nu-tests:
|
||||
env:
|
||||
@ -63,14 +77,14 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||
|
||||
python-virtualenv:
|
||||
std-lib-and-python-virtualenv:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
NU_LOG_LEVEL: DEBUG
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@ -87,10 +101,22 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: Install Nushell
|
||||
run: cargo install --locked --path=. --profile ci --no-default-features
|
||||
# prior to [*standard library: bring the tests into the main CI*](#8525)
|
||||
# there was a `--profile ci` here in the `cargo install`, as well as
|
||||
# `NUSHELL_CARGO_TARGET: ci` in the prelude above.
|
||||
#
|
||||
# this caused a "stackoverflow" error in the CI on windows,
|
||||
# see [this failing job](https://github.com/nushell/nushell/actions/runs/4512034615/jobs/7944945590)
|
||||
#
|
||||
# the CI profile has been removed in 00b820de9021227d1910a9ea388297ee7aee308e
|
||||
# as part of #8525.
|
||||
run: cargo install --path . --locked --no-default-features
|
||||
|
||||
- name: Standard library tests
|
||||
run: nu -c 'use std; std run-tests --path crates/nu-std'
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
@ -99,12 +125,20 @@ jobs:
|
||||
|
||||
- run: python -m pip install tox
|
||||
|
||||
# Get only the latest tagged version for stability reasons
|
||||
- name: Install virtualenv
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
|
||||
run: |
|
||||
cd virtualenv
|
||||
# if we encounter problems with bleeding edge tests pin to the latest tag
|
||||
# git checkout $(git describe --tags | cut -d - -f 1)
|
||||
# We need to disable failing on coverage levels.
|
||||
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
|
||||
mv patchproject.toml pyproject.toml
|
||||
tox -e ${{ matrix.py }} -- -k nushell
|
||||
shell: bash
|
||||
|
||||
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
|
||||
@ -126,10 +160,48 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --profile ci --package nu_plugin_*
|
||||
|
||||
|
||||
nu-coverage:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# disabled mac due to problems with merging coverage and similarity to linux
|
||||
# disabled windows due to running out of disk space when having too many crates or tests
|
||||
platform: [ubuntu-20.04] # windows-latest
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
|
||||
- name: Tests
|
||||
shell: bash
|
||||
run: |
|
||||
source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
|
||||
cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
|
||||
cargo build --workspace --profile ci
|
||||
cargo test --workspace --profile ci
|
||||
cargo llvm-cov report --profile ci --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: lcov.info
|
||||
|
41
.github/workflows/manual.yml
vendored
41
.github/workflows/manual.yml
vendored
@ -1,41 +0,0 @@
|
||||
# This is a basic workflow that is manually triggered
|
||||
# Don't run it unless you know what you are doing
|
||||
|
||||
name: Manual Workflow for Winget Submission
|
||||
|
||||
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||
# or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Inputs the workflow accepts.
|
||||
inputs:
|
||||
ver:
|
||||
# Friendly description to be shown in the UI instead of 'ver'
|
||||
description: 'The nushell version to release'
|
||||
# Default value if no value is explicitly provided
|
||||
default: '0.66.0'
|
||||
# Input has to be provided for the workflow to run
|
||||
required: true
|
||||
uri:
|
||||
# Friendly description to be shown in the UI instead of 'uri'
|
||||
description: 'The nushell windows .msi package URI to publish'
|
||||
# Default value if no value is explicitly provided
|
||||
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
|
||||
# Input has to be provided for the workflow to run
|
||||
required: true
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job
|
||||
rls-winget-pkg:
|
||||
name: Publish winget package manually
|
||||
# The type of runner that the job will run on
|
||||
runs-on: windows-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs commands using the runners shell
|
||||
- name: Submit package to Windows Package Manager Community Repository Manually
|
||||
run: |
|
||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}
|
91
.github/workflows/release-pkg.nu
vendored
91
.github/workflows/release-pkg.nu
vendored
@ -6,19 +6,38 @@
|
||||
# REF:
|
||||
# 1. https://github.com/volks73/cargo-wix
|
||||
|
||||
# Instructions for manually creating an MSI for Winget Releases when they fail
|
||||
# Added 2022-11-29 when Windows packaging wouldn't work
|
||||
# because softprops/action-gh-release was broken
|
||||
# To run this manual for windows
|
||||
# let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||
# let-env TARGET_RUSTFLAGS = ''
|
||||
# let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||
# Pass 1 let-env _EXTRA_ = 'bin'
|
||||
# Pass 2 let-env _EXTRA_ = 'msi'
|
||||
# Updated again on 2023-02-23 because msis are still failing validation
|
||||
# To run this manual for windows here are the steps I take
|
||||
# checkout the release you want to publish
|
||||
# 1. git checkout 0.76.0
|
||||
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
|
||||
# 2. $env:CARGO_TARGET_DIR = ""
|
||||
# 2. hide-env CARGO_TARGET_DIR
|
||||
# 3. let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||
# 4. let-env TARGET_RUSTFLAGS = ''
|
||||
# 5. let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||
# 6. let-env GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
|
||||
# 7. let-env OS = 'windows-latest'
|
||||
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
||||
# 8. let-env Path = ($env.Path | append 'c:\apps\7-zip')
|
||||
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
||||
# 9. let-env Path = ($env.Path | append 'c:\path\to\aria2c')
|
||||
# make sure you have the wixtools installed https://wixtoolset.org/
|
||||
# set os below like this because it's what github's runner is named
|
||||
# let os = 'windows-latest'
|
||||
# 10. let-env Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
||||
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
|
||||
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
|
||||
# 11. let-env _EXTRA_ = 'bin'
|
||||
# 12. source .github\workflows\release-pkg.nu
|
||||
# 13. cd ..
|
||||
# 14. let-env _EXTRA_ = 'msi'
|
||||
# 15. source .github\workflows\release-pkg.nu
|
||||
# After msi is generated, you have to update winget-pkgs repo, you'll need to patch the release
|
||||
# by deleting the existing msi and uploading this new msi. Then you'll need to update the hash
|
||||
# on the winget-pkgs PR. To generate the hash, run this command
|
||||
# 16. open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
|
||||
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
|
||||
|
||||
|
||||
# The main binary file to be released
|
||||
@ -31,17 +50,17 @@ let flags = $env.TARGET_RUSTFLAGS
|
||||
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
||||
let version = (open Cargo.toml | get package.version)
|
||||
|
||||
$'Debugging info:'
|
||||
print $'Debugging info:'
|
||||
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
||||
|
||||
# $env
|
||||
|
||||
let USE_UBUNTU = 'ubuntu-20.04'
|
||||
|
||||
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||
|
||||
$'Start building ($bin)...'; hr-line
|
||||
print $'Start building ($bin)...'; hr-line
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build for Ubuntu and macOS
|
||||
@ -51,25 +70,30 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
sudo apt update
|
||||
sudo apt-get install libxcb-composite0-dev -y
|
||||
}
|
||||
if $target == 'aarch64-unknown-linux-gnu' {
|
||||
match $target {
|
||||
'aarch64-unknown-linux-gnu' => {
|
||||
sudo apt-get install gcc-aarch64-linux-gnu -y
|
||||
let-env CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else if $target == 'armv7-unknown-linux-gnueabihf' {
|
||||
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
||||
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else if $target == 'riscv64gc-unknown-linux-gnu' {
|
||||
}
|
||||
'riscv64gc-unknown-linux-gnu' => {
|
||||
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||
let-env CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else {
|
||||
}
|
||||
'armv7-unknown-linux-gnueabihf' => {
|
||||
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
||||
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build for Windows without static-link-openssl feature
|
||||
@ -88,31 +112,34 @@ if $os in ['windows-latest'] {
|
||||
let suffix = if $os == 'windows-latest' { '.exe' }
|
||||
# nu, nu_plugin_* were all included
|
||||
let executable = $'target/($target)/release/($bin)*($suffix)'
|
||||
$'Current executable file: ($executable)'
|
||||
print $'Current executable file: ($executable)'
|
||||
|
||||
cd $src; mkdir $dist;
|
||||
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
|
||||
$'(char nl)All executable files:'; hr-line
|
||||
ls -f $executable
|
||||
print $'(char nl)All executable files:'; hr-line
|
||||
# We have to use `print` here to make sure the command output is displayed
|
||||
print (ls -f $executable); sleep 1sec
|
||||
|
||||
$'(char nl)Copying release files...'; hr-line
|
||||
print $'(char nl)Copying release files...'; hr-line
|
||||
cp -v README.release.txt $'($dist)/README.txt'
|
||||
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
||||
# Sleep a few seconds to make sure the cp process finished successfully
|
||||
sleep 3sec
|
||||
|
||||
$'(char nl)Check binary release version detail:'; hr-line
|
||||
print $'(char nl)Check binary release version detail:'; hr-line
|
||||
let ver = if $os == 'windows-latest' {
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str join
|
||||
}
|
||||
if ($ver | str trim | is-empty) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { $ver }
|
||||
print $'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { print $ver }
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Create a release archive and send it to output for the following steps
|
||||
# ----------------------------------------------------------------------------
|
||||
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
|
||||
let files = (ls | get name)
|
||||
@ -122,7 +149,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
mkdir $dest
|
||||
$files | each {|it| mv $it $dest } | ignore
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||
|
||||
tar -czf $archive $dest
|
||||
print $'archive: ---> ($archive)'; ls $archive
|
||||
@ -133,7 +160,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
|
||||
let releaseStem = $'($bin)-($version)-($target)'
|
||||
|
||||
$'(char nl)Download less related stuffs...'; hr-line
|
||||
print $'(char nl)Download less related stuffs...'; hr-line
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
@ -141,18 +168,18 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
if (get-env _EXTRA_) == 'msi' {
|
||||
|
||||
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
||||
$'(char nl)Start creating Windows msi package...'
|
||||
print $'(char nl)Start creating Windows msi package...'
|
||||
cd $src; hr-line
|
||||
# Wix need the binaries be stored in target/release/
|
||||
cp -r $'($dist)/*' target/release/
|
||||
cargo install cargo-wix --version 0.3.3
|
||||
cargo install cargo-wix --version 0.3.4
|
||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||
print $'archive: ---> ($wixRelease)';
|
||||
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
||||
|
||||
} else {
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
let archive = $'($dist)/($releaseStem).zip'
|
||||
7z a $archive *
|
||||
print $'archive: ---> ($archive)';
|
||||
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -63,19 +63,19 @@ jobs:
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Update Rust Toolchain Target
|
||||
run: |
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.72.1
|
||||
version: 0.78.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
28
.github/workflows/stale.yml
vendored
28
.github/workflows/stale.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
#on: [workflow_dispatch]
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
#debug-only: true
|
||||
ascending: true
|
||||
operations-per-run: 520
|
||||
enable-statistics: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
|
||||
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
|
||||
days-before-issue-stale: 90
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 100000
|
||||
days-before-pr-close: 100
|
||||
exempt-issue-labels: 'exempt,keep'
|
13
.github/workflows/typos.yml
vendored
Normal file
13
.github/workflows/typos.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Typos
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
21
.github/workflows/winget-submission.yml
vendored
21
.github/workflows/winget-submission.yml
vendored
@ -1,9 +1,14 @@
|
||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Specific tag name'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
|
||||
@ -12,8 +17,10 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
run: |
|
||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||
uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: Nushell.Nushell
|
||||
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||
token: ${{ secrets.NUSHELL_PAT }}
|
||||
fork-user: fdncred
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -22,6 +22,9 @@ debian/nu/
|
||||
# VSCode's IDE items
|
||||
.vscode/*
|
||||
|
||||
# JetBrains' Fleet IDE
|
||||
.fleet/*
|
||||
|
||||
# Visual Studio Extension SourceGear Rust items
|
||||
VSWorkspaceSettings.json
|
||||
unstable_cargo_features.txt
|
||||
@ -39,3 +42,10 @@ tarpaulin-report.html
|
||||
*.rsproj
|
||||
*.rsproj.user
|
||||
*.sln
|
||||
|
||||
# direnv
|
||||
.direnv/
|
||||
.envrc
|
||||
|
||||
# pre-commit-hooks
|
||||
.pre-commit-config.yaml
|
||||
|
12
.typos.toml
Normal file
12
.typos.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[files]
|
||||
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt"]
|
||||
|
||||
[default.extend-words]
|
||||
# Ignore false-positives
|
||||
nd = "nd"
|
||||
fo = "fo"
|
||||
ons = "ons"
|
||||
ba = "ba"
|
||||
Plasticos = "Plasticos"
|
||||
IIF = "IIF"
|
||||
numer = "numer"
|
115
CONTRIBUTING.md
115
CONTRIBUTING.md
@ -61,12 +61,22 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
```shell
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
use toolkit.nu clippy
|
||||
clippy
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --workspace
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
use toolkit.nu test
|
||||
test
|
||||
```
|
||||
|
||||
- Run all tests for a specific command
|
||||
|
||||
@ -79,12 +89,30 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
use toolkit.nu fmt
|
||||
fmt --check
|
||||
```
|
||||
|
||||
- Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
use toolkit.nu fmt
|
||||
fmt
|
||||
```
|
||||
|
||||
- Set up `git` hooks to check formatting and run `clippy` before committing and pushing:
|
||||
|
||||
```shell
|
||||
use toolkit.nu setup-git-hooks
|
||||
setup-git-hooks
|
||||
```
|
||||
_Unfortunately, this hook isn't available on Windows._
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
@ -99,3 +127,90 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
cargo run --release -- --log-level trace --log-target file
|
||||
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||
```
|
||||
|
||||
## Git etiquette
|
||||
|
||||
As nushell thrives on its broad base of volunteer contributors and maintainers with different backgrounds we have a few guidelines for how we best utilize git and GitHub for our contributions. We strive to balance three goals with those recommendations:
|
||||
|
||||
1. The **volunteer maintainers and contributors** can easily follow the changes you propose, gauge the impact, and come to help you or make a decision.
|
||||
2. **You as a contributor** can focus most of your time on improving the quality of the nushell project and contributing your expertise to the code or documentation.
|
||||
3. Making sure we can trace back *why* decisions were made in the past.
|
||||
This includes discarded approaches. Also we want to quickly identify regressions and fix when something broke.
|
||||
|
||||
### How we merge PRs
|
||||
|
||||
In general the maintainers **squash** all changes of your PR into a single commit when merging.
|
||||
|
||||
This keeps a clean enough linear history, while not forcing you to conform to a too strict style while iterating in your PR or fixing small problems. As an added benefit the commits on the `main` branch are tied to the discussion that happened in the PR through their `#1234` issue number.
|
||||
|
||||
> **Note**
|
||||
> **Pro advice:** In some circumstances, we can agree on rebase-merging a particularly large but connected PR as a series of atomic commits onto the `main` branch to ensure we can more easily revert or bisect particular aspects.
|
||||
|
||||
### A good PR makes a change!
|
||||
|
||||
As a result of this PR-centric strategy and the general goal that the reviewers should easily understand your change, the **PR title and description matters** a great deal!
|
||||
|
||||
Make sure your description is **concise** but contains all relevant information and context.
|
||||
This means demonstrating what changes, ideally through nushell code or output **examples**.
|
||||
Furthermore links to technical documentation or instructions for folks that want to play with your change make the review process much easier.
|
||||
|
||||
> **Note**
|
||||
> Try to follow the suggestions in our PR message template to make sure we can quickly focus on the technical merits and impact on the users.
|
||||
|
||||
#### A PR should limit itself to a single functional change or related set of same changes.
|
||||
|
||||
Mixing different changes in the same PR will make the review process much harder. A PR might get stuck on one aspect while we would actually like to land another change. Furthermore, if we are forced to revert a change, mixing and matching different aspects makes fixing bugs or regressions much harder.
|
||||
|
||||
Thus, please try to **separate out unrelated changes**!
|
||||
**Don't** mix unrelated refactors with a potentially contested change.
|
||||
Stylistic fixes and housekeeping can be bundled up into singular PRs.
|
||||
|
||||
#### Guidelines for the PR title
|
||||
|
||||
The PR title should be concise but contain everything for a contributor to know if they should help out in the review of this particular change.
|
||||
|
||||
**DON'T**
|
||||
- `Update file/in/some/deeply/nested/path.rs`
|
||||
- Why are you making this change?
|
||||
- `Fix 2134`
|
||||
- What has to be fixed?
|
||||
- Hard to follow when not online on GitHub.
|
||||
- ``Ignore `~` expansion``
|
||||
- In what context should this change take effect?
|
||||
- `[feature] refactor the whole parser and also make nushell indentation-sensitive, upgrade to using Cpython. Let me know what you think!`
|
||||
- Be concise
|
||||
- Maybe break up into smaller commits or PRs if the title already appears too long?
|
||||
|
||||
**DO**
|
||||
- Mention the nushell feature or command that is affected.
|
||||
- ``Fix URL parsing in `http get` (issue #1234)``
|
||||
- You can mention the issue number if other context is there.
|
||||
- In general, mention all related issues in the description to crosslink (e.g. `Fixes #1234`, `Closes #6789`)
|
||||
- For internal changes mention the area or symbols affected if it helps to clarify
|
||||
- ``Factor out `quote_string()` from parser to reuse in `explore` ``
|
||||
|
||||
### Review process / Merge conflicts
|
||||
|
||||
> **Note**
|
||||
> Keep in mind that the maintainers are volunteers that need to allocate their attention to several different areas and active PRs. We will try to get back to you as soon as possible.
|
||||
|
||||
You can help us to make the review process a smooth experience:
|
||||
- Testing:
|
||||
- We generally review in detail after all the tests pass. Let us know if there is a problem you want to discuss to fix a test failure or forces us to accept a breaking change.
|
||||
- If you fix a bug, it is highly recommended that you add a test that reproduces the original issue/panic in a minimal form.
|
||||
- In general, added tests help us to understand which assumptions go into a particular addition/change.
|
||||
- Try to also test corner cases where those assumptions might break. This can be more valuable than simply adding many similar tests.
|
||||
- Commit history inside a PR during code review:
|
||||
- Good **atomic commits** can help follow larger changes, but we are not pedantic.
|
||||
- We don't shame fixup commits while you try to figure out a problem. They can help others see what you tried and what didn't work. (see our [squash policy](#how-we-merge-prs))
|
||||
- During active review constant **force pushing** just to amend changes can be confusing!
|
||||
- GitHub's UI presents reviewers with less options to compare diffs
|
||||
- fetched branches for experimentation become invalid!
|
||||
- the notification a maintainer receives has a low signal-to-noise ratio
|
||||
- Git pros *can* use their judgement to rebase/squash to clean up the history *if it aids the understanding* of a larger change during review
|
||||
- Merge conflicts:
|
||||
- In general you should take care of resolving merge conflicts.
|
||||
- Use your judgement whether to `git merge main` or to `git rebase main`
|
||||
- Choose what simplifies having confidence in the conflict resolution and the review. **Merge commits in your branch are OK** in the squash model.
|
||||
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
|
||||
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
|
||||
|
2608
Cargo.lock
generated
2608
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
85
Cargo.toml
85
Cargo.toml
@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.73.0"
|
||||
version = "0.79.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -27,6 +27,7 @@ members = [
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-cmd-lang",
|
||||
"crates/nu-command",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-plugin",
|
||||
@ -35,40 +36,46 @@ members = [
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu_plugin_formats",
|
||||
"crates/nu-std",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
crossterm = "0.24.0"
|
||||
crossterm = "0.26"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.73.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.73.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.73.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.73.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.73.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.73.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.73.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.73.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.73.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.73.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.73.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.73.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.73.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.73.0" }
|
||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||
miette = { version = "5.7.0", features = ["fancy-no-backtrace"] }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.79.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.79.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.79.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.79.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.79.0" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.79.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.79.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.79.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.79.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.79.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.79.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.79.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.79.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.79.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.79.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.79.0" }
|
||||
|
||||
rayon = "1.5.1"
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.19.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
rayon = "1.7.0"
|
||||
is_executable = "1.0.1"
|
||||
simplelog = "0.12.0"
|
||||
time = "0.3.12"
|
||||
serde_json = "1.0"
|
||||
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
# Our dependencies don't use OpenSSL on Windows
|
||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
openssl = { version = "0.10.48", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
|
||||
@ -76,21 +83,34 @@ signal-hook = { version = "0.3.14", default-features = false }
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
|
||||
nix = { version = "0.26", default-features = false, features = [
|
||||
"signal",
|
||||
"process",
|
||||
"fs",
|
||||
"term",
|
||||
] }
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.73.0" }
|
||||
tempfile = "3.2.0"
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.79.0" }
|
||||
tempfile = "3.5.0"
|
||||
assert_cmd = "2.0.2"
|
||||
criterion = "0.4"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.8.0"
|
||||
serial_test = "1.0.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
rstest = { version = "0.17.0", default-features = false }
|
||||
itertools = "0.10.3"
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
plugin = [
|
||||
"nu-plugin",
|
||||
"nu-cli/plugin",
|
||||
"nu-parser/plugin",
|
||||
"nu-command/plugin",
|
||||
"nu-protocol/plugin",
|
||||
"nu-engine/plugin",
|
||||
]
|
||||
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||
extra = ["default"]
|
||||
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||
@ -135,8 +155,17 @@ debug = false
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
bench = false
|
||||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main"}
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Criterion benchmarking setup
|
||||
# Run all benchmarks with `cargo bench`
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
9
Cross.toml
Normal file
9
Cross.toml
Normal file
@ -0,0 +1,9 @@
|
||||
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||
# Run cross-rs like this:
|
||||
# cross build --target aarch64-unknown-linux-musl --release
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
Copyright (c) 2019 - 2023 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
|
||||
|
17
README.md
17
README.md
@ -1,11 +1,12 @@
|
||||
# Nushell <!-- omit in toc -->
|
||||
[](https://crates.io/crates/nu)
|
||||

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

|
||||

|
||||
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
||||
[](https://github.com/nushell/nushell/graphs/contributors)
|
||||
[](https://codecov.io/gh/nushell/nushell)
|
||||
|
||||
A new type of shell.
|
||||
|
||||
@ -32,7 +33,7 @@ This project has reached a minimum-viable-product level of quality. Many people
|
||||
|
||||
## Learning About Nu
|
||||
|
||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||
|
||||
@ -45,9 +46,11 @@ To quickly install Nu:
|
||||
brew install nushell
|
||||
# Windows
|
||||
winget install nushell
|
||||
# Cross Platform installation if you have node and npm installed, Note that nu plugins were not included
|
||||
npm install -g nushell
|
||||
```
|
||||
|
||||
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
|
||||
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||
|
||||
@ -174,6 +177,9 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins while the [showcase repo](https://github.com/nushell/showcase) *shows* off informative blog posts that have been written about Nushell along with videos that highlight technical
|
||||
topics that have been presented.
|
||||
|
||||
## Goals
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
@ -218,6 +224,7 @@ Please submit an issue or PR to be added to this list.
|
||||
- [oh-my-posh](https://ohmyposh.dev)
|
||||
- [Couchbase Shell](https://couchbase.sh)
|
||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||
- [atuin](https://github.com/ellie/atuin)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
7
benches/README.md
Normal file
7
benches/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Criterion benchmarks
|
||||
|
||||
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
||||
|
||||
Run all benchmarks with `cargo bench`
|
||||
|
||||
Or run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
191
benches/benchmarks.rs
Normal file
191
benches/benchmarks.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use nu_cli::eval_source;
|
||||
use nu_parser::parse;
|
||||
use nu_plugin::{EncodingType, PluginResponse};
|
||||
use nu_protocol::{PipelineData, Span, Value};
|
||||
use nu_utils::{get_default_config, get_default_env};
|
||||
|
||||
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
||||
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
||||
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||
// a way to split things up again.
|
||||
|
||||
fn parser_benchmarks(c: &mut Criterion) {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
|
||||
let default_env = get_default_env().as_bytes();
|
||||
c.bench_function("parse_default_env_file", |b| {
|
||||
b.iter_batched(
|
||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||
|mut working_set| parse(&mut working_set, None, default_env, false),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
let default_config = get_default_config().as_bytes();
|
||||
c.bench_function("parse_default_config_file", |b| {
|
||||
b.iter_batched(
|
||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||
|mut working_set| parse(&mut working_set, None, default_config, false),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
c.bench_function("eval default_env.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_env().as_bytes(),
|
||||
"default_env.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("eval default_config.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_config().as_bytes(),
|
||||
"default_config.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn eval_benchmarks(c: &mut Criterion) {
|
||||
c.bench_function("eval default_env.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_env().as_bytes(),
|
||||
"default_env.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("eval default_config.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_config().as_bytes(),
|
||||
"default_config.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
||||
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
|
||||
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
|
||||
|
||||
Value::List {
|
||||
vals: (0..row_cnt)
|
||||
.map(|_| Value::test_record(columns.clone(), vals.clone()))
|
||||
.collect(),
|
||||
span: Span::test_data(),
|
||||
}
|
||||
}
|
||||
|
||||
fn encoding_benchmarks(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Encoding");
|
||||
let test_cnt_pairs = [
|
||||
(100, 5),
|
||||
(100, 10),
|
||||
(100, 15),
|
||||
(1000, 5),
|
||||
(1000, 10),
|
||||
(1000, 15),
|
||||
(10000, 5),
|
||||
(10000, 10),
|
||||
(10000, 15),
|
||||
];
|
||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||
for fmt in ["json", "msgpack"] {
|
||||
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
||||
let mut res = vec![];
|
||||
let test_data =
|
||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
||||
});
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn decoding_benchmarks(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Decoding");
|
||||
let test_cnt_pairs = [
|
||||
(100, 5),
|
||||
(100, 10),
|
||||
(100, 15),
|
||||
(1000, 5),
|
||||
(1000, 10),
|
||||
(1000, 15),
|
||||
(10000, 5),
|
||||
(10000, 10),
|
||||
(10000, 15),
|
||||
];
|
||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||
for fmt in ["json", "msgpack"] {
|
||||
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
||||
let mut res = vec![];
|
||||
let test_data =
|
||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||
encoder.encode_response(&test_data, &mut res).unwrap();
|
||||
let mut binary_data = std::io::Cursor::new(res);
|
||||
b.iter(|| {
|
||||
binary_data.set_position(0);
|
||||
encoder.decode_response(&mut binary_data)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
parser_benchmarks,
|
||||
eval_benchmarks,
|
||||
encoding_benchmarks,
|
||||
decoding_benchmarks
|
||||
);
|
||||
criterion_main!(benches);
|
@ -5,7 +5,7 @@
|
||||
@echo.
|
||||
|
||||
echo Building nushell.exe
|
||||
cargo build cargo build --features=dataframe
|
||||
cargo build --features=dataframe
|
||||
@echo.
|
||||
|
||||
@cd crates\nu_plugin_example
|
||||
|
@ -12,6 +12,7 @@ let plugins = [
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
nu_plugin_formats,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
@ -19,5 +20,6 @@ for plugin in $plugins {
|
||||
'----------------------------'
|
||||
cd $'crates/($plugin)'
|
||||
cargo build
|
||||
cd ../../
|
||||
ignore
|
||||
}
|
||||
|
17
codecov.yml
Normal file
17
codecov.yml
Normal file
@ -0,0 +1,17 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 55%
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
comment:
|
||||
layout: reach, diff, files
|
||||
behavior: default
|
||||
require_base: yes
|
||||
require_head: yes
|
||||
after_n_builds: 1 # Disabled windows else: 2
|
||||
|
54
coverage-local.nu
Executable file
54
coverage-local.nu
Executable file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
let start = (date now)
|
||||
# Script to generate coverage locally
|
||||
#
|
||||
# Output: `lcov.info` file
|
||||
#
|
||||
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||
# https://github.com/taiki-e/cargo-llvm-cov
|
||||
|
||||
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||
# Else the script might stall waiting for your `y<ENTER>`
|
||||
|
||||
# Some of the internal tests rely on the exact cargo profile
|
||||
# (This is somewhat criminal itself)
|
||||
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||
let-env NUSHELL_CARGO_TARGET = "ci"
|
||||
|
||||
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||
|
||||
print "Setting up environment variables for coverage"
|
||||
# Enable LLVM coverage tracking through environment variables
|
||||
# show env outputs .ini/.toml style description of the variables
|
||||
# In order to use from toml, we need to make sure our string literals are single quoted
|
||||
# This is especially important when running on Windows since "C:\blah" is treated as an escape
|
||||
cargo llvm-cov show-env | str replace (char dq) (char sq) -a | from toml | load-env
|
||||
|
||||
print "Cleaning up coverage data"
|
||||
cargo llvm-cov clean --workspace
|
||||
|
||||
print "Building with workspace and profile=ci"
|
||||
# Apparently we need to explicitly build the necessary parts
|
||||
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||
# leads to smaller binaries and potential savings when compiling and running
|
||||
cargo build --workspace --profile=ci
|
||||
|
||||
print "Running tests with --workspace and profile=ci"
|
||||
cargo test --workspace --profile=ci
|
||||
|
||||
# You need to provide the used profile to find the raw data
|
||||
print "Generating coverage report as lcov.info"
|
||||
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||
|
||||
let end = (date now)
|
||||
$"Coverage generation took ($end - $start)."
|
||||
|
||||
# To display the coverage in your editor see:
|
||||
#
|
||||
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||
# - https://github.com/umaumax/vim-lcov
|
||||
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
38
coverage-local.sh
Executable file
38
coverage-local.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Script to generate coverage locally
|
||||
#
|
||||
# Output: `lcov.info` file
|
||||
#
|
||||
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||
# https://github.com/taiki-e/cargo-llvm-cov
|
||||
|
||||
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||
# Else the script might stall waiting for your `y<ENTER>`
|
||||
|
||||
# Some of the internal tests rely on the exact cargo profile
|
||||
# (This is somewhat criminal itself)
|
||||
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||
export NUSHELL_CARGO_TARGET=ci
|
||||
|
||||
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||
|
||||
# Enable LLVM coverage tracking through environment variables
|
||||
source <(cargo llvm-cov show-env --export-prefix)
|
||||
cargo llvm-cov clean --workspace
|
||||
# Apparently we need to explicitly build the necessary parts
|
||||
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||
# leads to smaller binaries and potential savings when compiling and running
|
||||
cargo build --workspace --profile=ci
|
||||
cargo test --workspace --profile=ci
|
||||
# You need to provide the used profile to find the raw data
|
||||
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||
|
||||
# To display the coverage in your editor see:
|
||||
#
|
||||
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||
# - https://github.com/umaumax/vim-lcov
|
||||
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
@ -5,35 +5,40 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.73.0"
|
||||
version = "0.79.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.73.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.73.0" }
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.79.0" }
|
||||
rstest = { version = "0.17.0", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.73.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.73.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.73.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.73.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.73.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.73.0" }
|
||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||
nu-command = { path = "../nu-command", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.79.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.79.0" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.19.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
atty = "0.2.14"
|
||||
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
||||
crossterm = "0.24.0"
|
||||
fancy-regex = "0.10.0"
|
||||
crossterm = "0.26"
|
||||
fancy-regex = "0.11.0"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
is_executable = "1.0.1"
|
||||
once_cell = "1.16.0"
|
||||
once_cell = "1.17.0"
|
||||
log = "0.4"
|
||||
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||
miette = { version = "5.7.0", features = ["fancy-no-backtrace"] }
|
||||
percent-encoding = "2"
|
||||
sysinfo = "0.26.2"
|
||||
sysinfo = "0.28.2"
|
||||
thiserror = "1.0.31"
|
||||
unicode-segmentation = "1.10.0"
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
Copyright (c) 2019 - 2023 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
|
||||
|
137
crates/nu-cli/src/commands/commandline.rs
Normal file
137
crates/nu-cli/src/commands/commandline.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::IntoPipelineData;
|
||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Commandline;
|
||||
|
||||
impl Command for Commandline {
|
||||
fn name(&self) -> &str {
|
||||
"commandline"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("commandline")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.switch(
|
||||
"cursor",
|
||||
"Set or get the current cursor position",
|
||||
Some('c'),
|
||||
)
|
||||
.switch(
|
||||
"append",
|
||||
"appends the string to the end of the buffer",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"insert",
|
||||
"inserts the string into the buffer at the cursor position",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"replace",
|
||||
"replaces the current contents of the buffer (default)",
|
||||
Some('r'),
|
||||
)
|
||||
.optional(
|
||||
"cmd",
|
||||
SyntaxShape::String,
|
||||
"the string to perform the operation with",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View or modify the current command line input buffer."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["repl", "interactive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||
let mut buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex");
|
||||
let mut cursor_pos = engine_state
|
||||
.repl_cursor_pos
|
||||
.lock()
|
||||
.expect("repl cursor pos mutex");
|
||||
|
||||
if call.has_flag("cursor") {
|
||||
let cmd_str = cmd.as_string()?;
|
||||
match cmd_str.parse::<i64>() {
|
||||
Ok(n) => {
|
||||
*cursor_pos = if n <= 0 {
|
||||
0usize
|
||||
} else {
|
||||
buffer
|
||||
.grapheme_indices(true)
|
||||
.map(|(i, _c)| i)
|
||||
.nth(n as usize)
|
||||
.unwrap_or(buffer.len())
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: "int".to_string(),
|
||||
from_type: "string".to_string(),
|
||||
span: cmd.span()?,
|
||||
help: Some(format!(
|
||||
r#"string "{cmd_str}" does not represent a valid integer"#
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if call.has_flag("append") {
|
||||
buffer.push_str(&cmd.as_string()?);
|
||||
} else if call.has_flag("insert") {
|
||||
let cmd_str = cmd.as_string()?;
|
||||
buffer.insert_str(*cursor_pos, &cmd_str);
|
||||
*cursor_pos += cmd_str.len();
|
||||
} else {
|
||||
*buffer = cmd.as_string()?;
|
||||
*cursor_pos = buffer.len();
|
||||
}
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
} else {
|
||||
let buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex");
|
||||
if call.has_flag("cursor") {
|
||||
let cursor_pos = engine_state
|
||||
.repl_cursor_pos
|
||||
.lock()
|
||||
.expect("repl cursor pos mutex");
|
||||
let char_pos = buffer
|
||||
.grapheme_indices(true)
|
||||
.position(|(i, _c)| i == *cursor_pos)
|
||||
.unwrap_or(buffer.len());
|
||||
Ok(Value::String {
|
||||
val: char_pos.to_string(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::String {
|
||||
val: buffer.to_string(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
crates/nu-cli/src/commands/default_context.rs
Normal file
33
crates/nu-cli/src/commands/default_context.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
|
||||
use crate::commands::*;
|
||||
|
||||
pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
||||
let delta = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
macro_rules! bind_command {
|
||||
( $( $command:expr ),* $(,)? ) => {
|
||||
$( working_set.add_decl(Box::new($command)); )*
|
||||
};
|
||||
}
|
||||
|
||||
bind_command! {
|
||||
Commandline,
|
||||
History,
|
||||
HistorySession,
|
||||
Keybindings,
|
||||
KeybindingsDefault,
|
||||
KeybindingsList,
|
||||
KeybindingsListen,
|
||||
};
|
||||
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
eprintln!("Error creating default context: {err:?}");
|
||||
}
|
||||
|
||||
engine_state
|
||||
}
|
@ -2,7 +2,7 @@ use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
|
||||
Signature, Span, Value,
|
||||
Signature, Span, Type, Value,
|
||||
};
|
||||
use reedline::{
|
||||
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
||||
@ -18,11 +18,16 @@ impl Command for History {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the command history"
|
||||
"Get the command history."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("history")
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::Table(vec![])),
|
||||
(Type::Nothing, Type::Nothing),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.switch("clear", "Clears out the history entries", Some('c'))
|
||||
.switch(
|
||||
"long",
|
||||
@ -38,7 +43,7 @@ impl Command for History {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
||||
@ -86,7 +91,7 @@ impl Command for History {
|
||||
match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::PlainText => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward))
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
@ -109,7 +114,7 @@ impl Command for History {
|
||||
.into_pipeline_data(ctrlc)),
|
||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward))
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, IntoPipelineData, PipelineData, Signature, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HistorySession;
|
||||
@ -11,11 +13,13 @@ impl Command for HistorySession {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the command history session"
|
||||
"Get the command history session."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("history session").category(Category::Misc)
|
||||
Signature::build("history session")
|
||||
.category(Category::Misc)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -32,7 +36,7 @@ impl Command for HistorySession {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::int(engine_state.history_session_id, call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,11 +14,17 @@ impl Command for Keybindings {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Platform)
|
||||
Signature::build(self.name())
|
||||
.category(Category::Platform)
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keybindings related commands"
|
||||
"Keybindings related commands."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -31,7 +37,7 @@ impl Command for Keybindings {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&Keybindings.signature(),
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
use reedline::get_reedline_default_keybindings;
|
||||
|
||||
@ -14,11 +14,13 @@ impl Command for KeybindingsDefault {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Platform)
|
||||
Signature::build(self.name())
|
||||
.category(Category::Platform)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List default keybindings"
|
||||
"List default keybindings."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -35,7 +37,7 @@ impl Command for KeybindingsDefault {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let records = get_reedline_default_keybindings()
|
||||
.into_iter()
|
||||
.map(|(mode, modifier, code, event)| {
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, Span, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
use reedline::{
|
||||
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
|
||||
@ -18,6 +18,7 @@ impl Command for KeybindingsList {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.switch("modifiers", "list of modifiers", Some('m'))
|
||||
.switch("keycodes", "list of keycodes", Some('k'))
|
||||
.switch("modes", "list of edit modes", Some('o'))
|
||||
@ -27,7 +28,7 @@ impl Command for KeybindingsList {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List available options that can be used to create keybindings"
|
||||
"List available options that can be used to create keybindings."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -56,7 +57,7 @@ impl Command for KeybindingsList {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let records = if call.named_len() == 0 {
|
||||
let all_options = vec!["modifiers", "keycodes", "edits", "modes", "events"];
|
||||
all_options
|
@ -3,7 +3,7 @@ use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
@ -20,7 +20,10 @@ impl Command for KeybindingsListen {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Platform)
|
||||
Signature::build(self.name())
|
||||
.category(Category::Platform)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -90,7 +93,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
||||
}
|
||||
terminal::disable_raw_mode()?;
|
||||
|
||||
Ok(Value::nothing(Span::test_data()))
|
||||
Ok(Value::nothing(Span::unknown()))
|
||||
}
|
||||
|
||||
// this fn is totally ripped off from crossterm's examples
|
||||
@ -99,7 +102,13 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
||||
// are printed, it's a good chance your terminal is eating
|
||||
// those events.
|
||||
fn print_events_helper(event: Event) -> Result<Value, ShellError> {
|
||||
if let Event::Key(KeyEvent { code, modifiers }) = event {
|
||||
if let Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers,
|
||||
kind,
|
||||
state,
|
||||
}) = event
|
||||
{
|
||||
match code {
|
||||
KeyCode::Char(c) => {
|
||||
let record = Value::Record {
|
||||
@ -108,14 +117,18 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
|
||||
"code".into(),
|
||||
"modifier".into(),
|
||||
"flags".into(),
|
||||
"kind".into(),
|
||||
"state".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(format!("{}", c), Span::test_data()),
|
||||
Value::string(format!("{:#08x}", u32::from(c)), Span::test_data()),
|
||||
Value::string(format!("{:?}", modifiers), Span::test_data()),
|
||||
Value::string(format!("{:#08b}", modifiers), Span::test_data()),
|
||||
Value::string(format!("{c}"), Span::unknown()),
|
||||
Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
|
||||
Value::string(format!("{modifiers:?}"), Span::unknown()),
|
||||
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
||||
Value::string(format!("{kind:?}"), Span::unknown()),
|
||||
Value::string(format!("{state:?}"), Span::unknown()),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
Ok(record)
|
||||
}
|
||||
@ -123,11 +136,11 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
|
||||
let record = Value::Record {
|
||||
cols: vec!["code".into(), "modifier".into(), "flags".into()],
|
||||
vals: vec![
|
||||
Value::string(format!("{:?}", code), Span::test_data()),
|
||||
Value::string(format!("{:?}", modifiers), Span::test_data()),
|
||||
Value::string(format!("{:#08b}", modifiers), Span::test_data()),
|
||||
Value::string(format!("{code:?}"), Span::unknown()),
|
||||
Value::string(format!("{modifiers:?}"), Span::unknown()),
|
||||
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
Ok(record)
|
||||
}
|
||||
@ -135,20 +148,9 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
|
||||
} else {
|
||||
let record = Value::Record {
|
||||
cols: vec!["event".into()],
|
||||
vals: vec![Value::string(format!("{:?}", event), Span::test_data())],
|
||||
span: Span::test_data(),
|
||||
vals: vec![Value::string(format!("{event:?}"), Span::unknown())],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
Ok(record)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::KeybindingsListen;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::test_examples;
|
||||
test_examples(KeybindingsListen {})
|
||||
}
|
||||
}
|
@ -1,9 +1,18 @@
|
||||
mod commandline;
|
||||
mod default_context;
|
||||
mod history;
|
||||
mod history_session;
|
||||
mod keybindings;
|
||||
mod keybindings_default;
|
||||
mod keybindings_list;
|
||||
mod keybindings_listen;
|
||||
|
||||
pub use commandline::Commandline;
|
||||
pub use history::History;
|
||||
pub use history_session::HistorySession;
|
||||
pub use keybindings::Keybindings;
|
||||
pub use keybindings_default::KeybindingsDefault;
|
||||
pub use keybindings_list::KeybindingsList;
|
||||
pub use keybindings_listen::KeybindingsListen;
|
||||
|
||||
pub use default_context::add_cli_context;
|
@ -37,7 +37,8 @@ impl CommandCompletion {
|
||||
) -> Vec<String> {
|
||||
let mut executables = vec![];
|
||||
|
||||
let paths = self.engine_state.get_env_var("PATH");
|
||||
// os agnostic way to get the PATH env var
|
||||
let paths = self.engine_state.get_path_env_var();
|
||||
|
||||
if let Some(paths) = paths {
|
||||
if let Ok(paths) = paths.as_list() {
|
||||
@ -87,7 +88,7 @@ impl CommandCompletion {
|
||||
|
||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||
|
||||
let results = working_set
|
||||
let mut results = working_set
|
||||
.find_commands_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
@ -96,20 +97,8 @@ impl CommandCompletion {
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let results_aliases = working_set
|
||||
.find_aliases_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: String::from_utf8_lossy(&x).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
@ -168,7 +157,7 @@ impl Completer for CommandCompletion {
|
||||
.take_while(|x| {
|
||||
matches!(
|
||||
x.1,
|
||||
FlatShape::InternalCall
|
||||
FlatShape::InternalCall(_)
|
||||
| FlatShape::External
|
||||
| FlatShape::ExternalArg
|
||||
| FlatShape::Literal
|
||||
@ -196,8 +185,9 @@ impl Completer for CommandCompletion {
|
||||
|
||||
let config = working_set.get_config();
|
||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall(_))
|
||||
|| ((span.end - span.start) == 0)
|
||||
|| is_passthrough_command(working_set.delta.get_file_contents())
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||
@ -225,3 +215,107 @@ impl Completer for CommandCompletion {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
|
||||
match contents.get(start..) {
|
||||
Some(contents) => {
|
||||
contents
|
||||
.iter()
|
||||
.take_while(|x| x.is_ascii_whitespace())
|
||||
.count()
|
||||
+ start
|
||||
}
|
||||
None => start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usize)]) -> bool {
|
||||
for (contents, _, _) in working_set_file_contents {
|
||||
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
||||
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
||||
|
||||
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
||||
|
||||
let result = match contents.get(cur_pos..) {
|
||||
Some(contents) => contents.starts_with(b"sudo "),
|
||||
None => false,
|
||||
};
|
||||
if result {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod command_completions_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_find_non_whitespace_index() {
|
||||
let commands = vec![
|
||||
(" hello", 4),
|
||||
("sudo ", 0),
|
||||
(" sudo ", 2),
|
||||
(" sudo ", 2),
|
||||
(" hello ", 1),
|
||||
(" hello ", 3),
|
||||
(" hello | sudo ", 4),
|
||||
(" sudo|sudo", 5),
|
||||
("sudo | sudo ", 0),
|
||||
(" hello sud", 1),
|
||||
];
|
||||
for (idx, ele) in commands.iter().enumerate() {
|
||||
let index = find_non_whitespace_index(&Vec::from(ele.0.as_bytes()), 0);
|
||||
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_last_command_passthrough() {
|
||||
let commands = vec![
|
||||
(" hello", false),
|
||||
(" sudo ", true),
|
||||
("sudo ", true),
|
||||
(" hello", false),
|
||||
(" sudo", false),
|
||||
(" sudo ", true),
|
||||
(" sudo ", true),
|
||||
(" sudo ", true),
|
||||
(" hello ", false),
|
||||
(" hello | sudo ", true),
|
||||
(" sudo|sudo", false),
|
||||
("sudo | sudo ", true),
|
||||
(" hello sud", false),
|
||||
(" sudo | sud ", false),
|
||||
(" sudo|sudo ", true),
|
||||
(" sudo | sudo ls | sudo ", true),
|
||||
];
|
||||
for (idx, ele) in commands.iter().enumerate() {
|
||||
let input = ele.0.as_bytes();
|
||||
|
||||
let mut engine_state = EngineState::new();
|
||||
engine_state.add_file("test.nu".into(), vec![]);
|
||||
|
||||
let delta = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let _ = working_set.add_file("child.nu".into(), input);
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let result = engine_state.merge_delta(delta);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Merge delta has failed: {}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
|
||||
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
|
||||
assert_eq!(
|
||||
is_passthrough_command, ele.1,
|
||||
"index for '{}': {}",
|
||||
ele.0, idx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ impl NuCompleter {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed to eval completer block: {}", err),
|
||||
Err(err) => println!("failed to eval completer block: {err}"),
|
||||
}
|
||||
|
||||
None
|
||||
@ -113,14 +113,13 @@ impl NuCompleter {
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||
let initial_line = line.to_string();
|
||||
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||
new_line.insert(alias_total_offset + pos, b'a');
|
||||
let mut line = line.to_string();
|
||||
line.insert(pos, 'a');
|
||||
let pos = offset + pos;
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for pipeline_element in pipeline.elements {
|
||||
@ -128,12 +127,16 @@ impl NuCompleter {
|
||||
PipelineElement::Expression(_, expr)
|
||||
| PipelineElement::Redirection(_, _, expr)
|
||||
| PipelineElement::And(_, expr)
|
||||
| PipelineElement::Or(_, expr) => {
|
||||
| PipelineElement::Or(_, expr)
|
||||
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
let mut spans: Vec<String> = vec![];
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
let is_passthrough_command = spans
|
||||
.first()
|
||||
.filter(|content| *content == &String::from("sudo"))
|
||||
.is_some();
|
||||
// Read the current spam to string
|
||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||
@ -149,24 +152,17 @@ impl NuCompleter {
|
||||
}
|
||||
|
||||
// Complete based on the last span
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
if pos >= flat.0.start && pos < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
|
||||
// Create a new span
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span::new(flat.0.start, flat.0.end - 1 - span_offset)
|
||||
} else {
|
||||
Span::new(
|
||||
flat.0.start - span_offset,
|
||||
flat.0.end - 1 - span_offset,
|
||||
)
|
||||
};
|
||||
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
||||
|
||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
let index = pos - (flat.0.start - span_offset);
|
||||
let index = pos - flat.0.start;
|
||||
prefix.drain(index..);
|
||||
|
||||
// Variables completion
|
||||
@ -216,8 +212,9 @@ impl NuCompleter {
|
||||
}
|
||||
|
||||
// specially check if it is currently empty - always complete commands
|
||||
if flat_idx == 0
|
||||
&& working_set.get_span_contents(new_span).is_empty()
|
||||
if (is_passthrough_command && flat_idx == 1)
|
||||
|| (flat_idx == 0
|
||||
&& working_set.get_span_contents(new_span).is_empty())
|
||||
{
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
@ -238,7 +235,7 @@ impl NuCompleter {
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||
if flat_idx > 0 {
|
||||
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 {
|
||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||
// Read the content for the previous expression
|
||||
let prev_expr_str =
|
||||
@ -385,85 +382,6 @@ impl ReedlineCompleter for NuCompleter {
|
||||
}
|
||||
}
|
||||
|
||||
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||
|
||||
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||
// that `g` is an alias of `git`
|
||||
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
|
||||
// An vector represents the offsets of alias
|
||||
// e.g: the offset is 2 for the alias `g` of `git`
|
||||
let mut alias_offset = vec![];
|
||||
let mut output = vec![];
|
||||
if let Some(matched_alias) = search_alias(line, working_set) {
|
||||
let mut lens = matched_alias.len();
|
||||
for (input_vec, line_vec) in matched_alias {
|
||||
alias_offset.push(line_vec.len() - input_vec.len());
|
||||
output.extend(line_vec);
|
||||
if lens > 1 {
|
||||
output.push(b' ');
|
||||
lens -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_empty() {
|
||||
let last = line.last().expect("input is empty");
|
||||
if last == &b' ' {
|
||||
output.push(b' ');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output = line.to_vec();
|
||||
}
|
||||
|
||||
(output, alias_offset)
|
||||
}
|
||||
|
||||
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||
let mut vec_names = vec![];
|
||||
let mut vec_alias = vec![];
|
||||
let mut pos = 0;
|
||||
let mut is_alias = false;
|
||||
for (index, character) in input.iter().enumerate() {
|
||||
if *character == b' ' {
|
||||
let range = &input[pos..index];
|
||||
vec_names.push(range.to_owned());
|
||||
pos = index + 1;
|
||||
}
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push(input[pos..].to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||
let alias_span = working_set.get_alias(alias_id);
|
||||
let mut span_vec = vec![];
|
||||
is_alias = true;
|
||||
for alias in alias_span {
|
||||
let name = working_set.get_span_contents(*alias);
|
||||
if !name.is_empty() {
|
||||
span_vec.push(name);
|
||||
}
|
||||
}
|
||||
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
|
||||
let full_aliases = span_vec.join(&[b' '][..]);
|
||||
vec_alias.push(full_aliases);
|
||||
} else {
|
||||
vec_alias.push(name.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
if is_alias {
|
||||
// Zip names and alias vectors, the original inputs and its aliases mapping.
|
||||
// e.g:(['g'], ['g','i','t'])
|
||||
let output = vec_names.into_iter().zip(vec_alias).collect();
|
||||
Some(output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// reads the most left variable returning it's name (e.g: $myvar)
|
||||
// and the depth (a.b.c)
|
||||
fn most_left_variable(
|
||||
@ -484,7 +402,7 @@ fn most_left_variable(
|
||||
let result = working_set.get_span_contents(item.0).to_vec();
|
||||
|
||||
match item.1 {
|
||||
FlatShape::Variable => {
|
||||
FlatShape::Variable(_) => {
|
||||
variables_found.push(result);
|
||||
found_var = true;
|
||||
|
||||
@ -575,3 +493,63 @@ pub fn map_value_completions<'a>(
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod completer_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_completion_helper() {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
|
||||
// Custom additions
|
||||
let delta = {
|
||||
let working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let result = engine_state.merge_delta(delta);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Error merging delta: {:?}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
|
||||
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
||||
let dataset = vec![
|
||||
("sudo", false, "", Vec::new()),
|
||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||
(" sudo", false, "", Vec::new()),
|
||||
(" sudo le", true, "le", vec!["let", "length"]),
|
||||
(
|
||||
"ls | c",
|
||||
true,
|
||||
"c",
|
||||
vec!["cd", "config", "const", "cp", "cal"],
|
||||
),
|
||||
("ls | sudo m", true, "m", vec!["mv", "mut", "move"]),
|
||||
];
|
||||
for (line, has_result, begins_with, expected_values) in dataset {
|
||||
let result = completer.completion_helper(line, line.len());
|
||||
// Test whether the result is empty or not
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
|
||||
|
||||
// Test whether the result begins with the expected value
|
||||
result
|
||||
.iter()
|
||||
.for_each(|x| assert!(x.value.starts_with(begins_with)));
|
||||
|
||||
// Test whether the result contains all the expected values
|
||||
assert_eq!(
|
||||
result
|
||||
.iter()
|
||||
.map(|x| expected_values.contains(&x.value.as_str()))
|
||||
.filter(|x| *x)
|
||||
.count(),
|
||||
expected_values.len(),
|
||||
"line: {}",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||
PipelineData, Span, Type, Value,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use super::completer::map_value_completions;
|
||||
|
||||
@ -66,6 +66,7 @@ impl Completer for CustomCompletion {
|
||||
],
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: true,
|
||||
parser_info: HashMap::new(),
|
||||
},
|
||||
PipelineData::empty(),
|
||||
);
|
||||
@ -73,8 +74,8 @@ impl Completer for CustomCompletion {
|
||||
let mut custom_completion_options = None;
|
||||
|
||||
// Parse result
|
||||
let suggestions = match result {
|
||||
Ok(pd) => {
|
||||
let suggestions = result
|
||||
.map(|pd| {
|
||||
let value = pd.into_value(span);
|
||||
match &value {
|
||||
Value::Record { .. } => {
|
||||
@ -131,9 +132,8 @@ impl Completer for CustomCompletion {
|
||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
|
@ -8,7 +8,7 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{partial_from, prepend_base_dir};
|
||||
use super::{partial_from, prepend_base_dir, SortBy};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
@ -33,14 +33,7 @@ impl Completer for DirectoryCompletion {
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let cwd = self.engine_state.current_work_dir();
|
||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Filter only the folders
|
||||
@ -67,12 +60,20 @@ impl Completer for DirectoryCompletion {
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
|
||||
match self.get_sort_by() {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
@ -126,7 +127,7 @@ pub fn directory_completion(
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
format!("{base_dir_name}{file_name}")
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
@ -142,7 +143,7 @@ pub fn directory_completion(
|
||||
|| path.contains(' ')
|
||||
|| path.contains('#')
|
||||
{
|
||||
path = format!("`{}`", path);
|
||||
path = format!("`{path}`");
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
|
@ -58,7 +58,7 @@ impl Completer for DotNuCompletion {
|
||||
};
|
||||
|
||||
// Check if the base_dir is a folder
|
||||
if base_dir != format!(".{}", SEP) {
|
||||
if base_dir != format!(".{SEP}") {
|
||||
// Add the base dir into the directories to be searched
|
||||
search_dirs.push(base_dir.clone());
|
||||
|
||||
@ -70,14 +70,7 @@ impl Completer for DotNuCompletion {
|
||||
partial = base_dir_partial;
|
||||
} else {
|
||||
// Fetch the current folder
|
||||
let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let current_folder = self.engine_state.current_work_dir();
|
||||
is_current_folder = true;
|
||||
|
||||
// Add the current folder and the lib dirs into the
|
||||
|
@ -7,6 +7,8 @@ use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SortBy;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -30,14 +32,7 @@ impl Completer for FileCompletion {
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let cwd = self.engine_state.current_work_dir();
|
||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
||||
.into_iter()
|
||||
@ -62,12 +57,20 @@ impl Completer for FileCompletion {
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
|
||||
match self.get_sort_by() {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
@ -131,7 +134,7 @@ pub fn file_path_completion(
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
format!("{base_dir_name}{file_name}")
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
@ -146,8 +149,20 @@ pub fn file_path_completion(
|
||||
|| path.contains('"')
|
||||
|| path.contains(' ')
|
||||
|| path.contains('#')
|
||||
|| path.contains('(')
|
||||
|| path.contains(')')
|
||||
|| path.starts_with('0')
|
||||
|| path.starts_with('1')
|
||||
|| path.starts_with('2')
|
||||
|| path.starts_with('3')
|
||||
|| path.starts_with('4')
|
||||
|| path.starts_with('5')
|
||||
|| path.starts_with('6')
|
||||
|| path.starts_with('7')
|
||||
|| path.starts_with('8')
|
||||
|| path.starts_with('9')
|
||||
{
|
||||
path = format!("`{}`", path);
|
||||
path = format!("`{path}`");
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
@ -175,7 +190,7 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||
|
||||
/// Returns whether the base_dir should be prepended to the file path
|
||||
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
||||
if base_dir == format!(".{}", SEP) {
|
||||
if base_dir == format!(".{SEP}") {
|
||||
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
||||
// input already includes a local folder prefix.
|
||||
let manually_entered = {
|
||||
|
@ -9,6 +9,8 @@ use reedline::Suggestion;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::MatchAlgorithm;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||
@ -73,10 +75,11 @@ impl Completer for VariableCompletion {
|
||||
for suggestion in
|
||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -86,10 +89,11 @@ impl Completer for VariableCompletion {
|
||||
} else {
|
||||
// No nesting provided, return all env vars
|
||||
for env_var in env_vars {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(env_var.0.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
env_var.0.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
@ -116,10 +120,11 @@ impl Completer for VariableCompletion {
|
||||
for suggestion in
|
||||
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -138,10 +143,11 @@ impl Completer for VariableCompletion {
|
||||
for suggestion in
|
||||
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -153,10 +159,11 @@ impl Completer for VariableCompletion {
|
||||
|
||||
// Variable completion (e.g: $en<tab> to complete $env)
|
||||
for builtin in builtins {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(builtin.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
builtin.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
@ -172,13 +179,13 @@ impl Completer for VariableCompletion {
|
||||
let mut removed_overlays = vec![];
|
||||
// Working set scope vars
|
||||
for scope_frame in working_set.delta.scope.iter().rev() {
|
||||
for overlay_frame in scope_frame
|
||||
.active_overlays(&mut removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
@ -193,14 +200,13 @@ impl Completer for VariableCompletion {
|
||||
|
||||
// Permanent state vars
|
||||
// for scope in &self.engine_state.scope {
|
||||
for overlay_frame in self
|
||||
.engine_state
|
||||
.active_overlays(&removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for overlay_frame in self.engine_state.active_overlays(&removed_overlays).rev() {
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
@ -247,6 +253,20 @@ fn nested_suggestions(
|
||||
|
||||
output
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
// Add all the columns as completion
|
||||
for column_name in val.column_names() {
|
||||
output.push(Suggestion {
|
||||
value: column_name.to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
_ => output,
|
||||
}
|
||||
@ -281,3 +301,13 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
impl MatchAlgorithm {
|
||||
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
|
||||
if sensitive {
|
||||
self.matches_u8(haystack, needle)
|
||||
} else {
|
||||
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_parser::ParseError;
|
||||
use crate::util::eval_source;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::report_error;
|
||||
use nu_protocol::{HistoryFileFormat, PipelineData};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::{ParseError, Spanned};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_utils::utils::perf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
@ -24,6 +23,8 @@ pub fn read_plugin_file(
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
let mut plug_path = String::new();
|
||||
// Reading signatures from signature file
|
||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||
@ -31,7 +32,7 @@ pub fn read_plugin_file(
|
||||
let plugin_path = engine_state.plugin_signatures.clone();
|
||||
if let Some(plugin_path) = plugin_path {
|
||||
let plugin_filename = plugin_path.to_string_lossy();
|
||||
|
||||
plug_path = plugin_filename.to_string();
|
||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
@ -39,11 +40,19 @@ pub fn read_plugin_file(
|
||||
&contents,
|
||||
&plugin_filename,
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
perf(
|
||||
&format!("read_plugin_file {}", &plug_path),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
@ -56,13 +65,12 @@ pub fn add_plugin_file(
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
match canonicalize_with(&plugin_file.item, cwd) {
|
||||
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||
Err(_) => {
|
||||
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
||||
engine_state.plugin_signatures = Some(path)
|
||||
} else {
|
||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
@ -86,6 +94,7 @@ pub fn eval_config_contents(
|
||||
&contents,
|
||||
&config_filename,
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Merge the environment in case env vars changed in the config
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::util::report_error;
|
||||
use log::info;
|
||||
use miette::Result;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::report_error;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
@ -34,9 +34,9 @@ pub fn evaluate_commands(
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (output, err) = parse(&mut working_set, None, commands.item.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
use crate::util::eval_source;
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_engine::{convert_env_values, current_dir};
|
||||
use nu_parser::parse;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::report_error;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
@ -29,10 +30,7 @@ pub fn evaluate_file(
|
||||
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let file_path = {
|
||||
match canonicalize_with(&path, &cwd) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
@ -42,13 +40,9 @@ pub fn evaluate_file(
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let file_path_str = match file_path.to_str() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let file_path_str = file_path.to_str().unwrap_or_else(|| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
@ -61,12 +55,11 @@ pub fn evaluate_file(
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let file = match std::fs::read(&file_path).into_diagnostic() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let file = std::fs::read(&file_path)
|
||||
.into_diagnostic()
|
||||
.unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
@ -80,22 +73,34 @@ pub fn evaluate_file(
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
engine_state.start_in_file(Some(file_path_str));
|
||||
|
||||
let mut parent = file_path.clone();
|
||||
parent.pop();
|
||||
let parent = file_path.parent().unwrap_or_else(|| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::FileNotFoundCustom(
|
||||
format!("The file path '{file_path_str}' does not have a parent"),
|
||||
Span::unknown(),
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
stack.add_env_var(
|
||||
"FILE_PWD".to_string(),
|
||||
Value::string(parent.to_string_lossy(), Span::unknown()),
|
||||
);
|
||||
stack.add_env_var(
|
||||
"CURRENT_FILE".to_string(),
|
||||
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
||||
);
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
trace!("parsing file: {}", file_path_str);
|
||||
let _ = parse(&mut working_set, Some(file_path_str), &file, false, &[]);
|
||||
let _ = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||
|
||||
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||
let args = format!("main {}", args.join(" "));
|
||||
@ -106,13 +111,21 @@ pub fn evaluate_file(
|
||||
&file,
|
||||
file_path_str,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
args.as_bytes(),
|
||||
"<commandline>",
|
||||
input,
|
||||
true,
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if !eval_source(engine_state, stack, &file, file_path_str, input) {
|
||||
} else if !eval_source(engine_state, stack, &file, file_path_str, input, true) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@ -121,7 +134,7 @@ pub fn evaluate_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_table_or_error(
|
||||
pub(crate) fn print_table_or_error(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
@ -137,24 +150,19 @@ pub fn print_table_or_error(
|
||||
|
||||
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, error);
|
||||
|
||||
report_error(&working_set, &**error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
Some(decl_id) => {
|
||||
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
} else {
|
||||
let table = command.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
// The final call on table command, it's ok to set redirect_output to false.
|
||||
let mut call = Call::new(Span::new(0, 0));
|
||||
call.redirect_stdout = false;
|
||||
let table = command.run(engine_state, stack, &call, pipeline_data);
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
@ -162,18 +170,14 @@ pub fn print_table_or_error(
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
} else {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
@ -194,14 +198,12 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
report_error(&working_set, &*error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", config);
|
||||
out.push('\n');
|
||||
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||
let out = item.into_string("\n", config) + "\n";
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod commands;
|
||||
mod completions;
|
||||
mod config_files;
|
||||
mod eval_cmds;
|
||||
mod eval_file;
|
||||
mod menus;
|
||||
mod nu_highlight;
|
||||
@ -13,18 +14,19 @@ mod syntax_highlight;
|
||||
mod util;
|
||||
mod validation;
|
||||
|
||||
pub use commands::evaluate_commands;
|
||||
pub use commands::add_cli_context;
|
||||
pub use completions::{FileCompletion, NuCompleter};
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use eval_cmds::evaluate_commands;
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
||||
pub use nu_command::util::get_init_cwd;
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use repl::{eval_env_change_hook, eval_hook};
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||
pub use util::{eval_source, gather_parent_env_vars};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -411,10 +411,10 @@ impl DescriptionMenu {
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
format!(" {example}\r\n")
|
||||
}
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
format!(" {example}\r\n")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -429,7 +429,7 @@ impl DescriptionMenu {
|
||||
examples,
|
||||
)
|
||||
} else {
|
||||
format!("\r\n\r\nExamples:\r\n{}", examples,)
|
||||
format!("\r\n\r\nExamples:\r\n{examples}",)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,13 +81,10 @@ fn convert_to_suggestions(
|
||||
) -> Vec<Suggestion> {
|
||||
match value {
|
||||
Value::Record { .. } => {
|
||||
let text = match value
|
||||
let text = value
|
||||
.get_data_by_key("value")
|
||||
.and_then(|val| val.as_string().ok())
|
||||
{
|
||||
Some(val) => val,
|
||||
None => "No value key".to_string(),
|
||||
};
|
||||
.unwrap_or_else(|| "No value key".to_string());
|
||||
|
||||
let description = value
|
||||
.get_data_by_key("description")
|
||||
@ -157,7 +154,7 @@ fn convert_to_suggestions(
|
||||
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||
.collect(),
|
||||
_ => vec![Suggestion {
|
||||
value: format!("Not a record: {:?}", value),
|
||||
value: format!("Not a record: {value:?}"),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
||||
use reedline::Highlighter;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -12,7 +12,9 @@ impl Command for NuHighlight {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight").category(Category::Strings)
|
||||
Signature::build("nu-highlight")
|
||||
.category(Category::Strings)
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -33,7 +35,7 @@ impl Command for NuHighlight {
|
||||
let head = call.head;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let engine_state = std::sync::Arc::new(engine_state.clone());
|
||||
let config = engine_state.get_config().clone();
|
||||
|
||||
let highlighter = crate::NuHighlighter {
|
||||
@ -51,7 +53,9 @@ impl Command for NuHighlight {
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(err) => Value::Error { error: err },
|
||||
Err(err) => Value::Error {
|
||||
error: Box::new(err),
|
||||
},
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
|
@ -2,7 +2,8 @@ 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,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -15,6 +16,7 @@ impl Command for Print {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("print")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||
.switch(
|
||||
"no-newline",
|
||||
@ -26,7 +28,7 @@ impl Command for Print {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the given values to stdout"
|
||||
"Print the given values to stdout."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
@ -45,16 +47,21 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag("no-newline");
|
||||
let to_stderr = call.has_flag("stderr");
|
||||
|
||||
// This will allow for easy printing of pipelines as well
|
||||
if !args.is_empty() {
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
} else if !input.is_nothing() {
|
||||
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ impl NushellPrompt {
|
||||
}
|
||||
|
||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||
format!("({})", str)
|
||||
format!("({str})")
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ impl Prompt for NushellPrompt {
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
let default = DefaultPrompt::default();
|
||||
default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
@ -118,7 +118,7 @@ impl Prompt for NushellPrompt {
|
||||
if let Some(prompt_string) = &self.right_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
let default = DefaultPrompt::default();
|
||||
default
|
||||
.render_prompt_right()
|
||||
.to_string()
|
||||
@ -130,32 +130,36 @@ impl Prompt for NushellPrompt {
|
||||
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||
match edit_mode {
|
||||
PromptEditMode::Default => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => ": ".into(),
|
||||
Some(indicator) => indicator,
|
||||
None => ": ",
|
||||
},
|
||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
match &self.default_multiline_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "::: ".into(),
|
||||
Some(indicator) => indicator,
|
||||
None => "::: ",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::util::report_error;
|
||||
use crate::NushellPrompt;
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_protocol::report_error;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Value,
|
||||
@ -39,41 +39,37 @@ fn get_prompt_string(
|
||||
// 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::empty());
|
||||
info!(
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
match ret_val {
|
||||
Ok(ret_val) => Some(ret_val),
|
||||
Err(err) => {
|
||||
ret_val
|
||||
.map_err(|err| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::Block { val: block_id, .. } => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
||||
info!(
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
match ret_val {
|
||||
Ok(ret_val) => Some(ret_val),
|
||||
Err(err) => {
|
||||
ret_val
|
||||
.map_err(|err| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||
_ => None,
|
||||
@ -81,8 +77,7 @@ fn get_prompt_string(
|
||||
.and_then(|pipeline_data| {
|
||||
let output = pipeline_data.collect_string("", config).ok();
|
||||
|
||||
match output {
|
||||
Some(mut x) => {
|
||||
output.map(|mut x| {
|
||||
// Just remove the very last newline.
|
||||
if x.ends_with('\n') {
|
||||
x.pop();
|
||||
@ -91,10 +86,8 @@ fn get_prompt_string(
|
||||
if x.ends_with('\r') {
|
||||
x.pop();
|
||||
}
|
||||
Some(x)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
x
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -111,12 +104,12 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
match left_prompt_string {
|
||||
Some(prompt_string) => Some(format!(
|
||||
"{}{}{}",
|
||||
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||
)),
|
||||
None => left_prompt_string,
|
||||
if let Some(prompt_string) = left_prompt_string {
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
||||
))
|
||||
} else {
|
||||
left_prompt_string
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
@ -148,7 +141,7 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
);
|
||||
|
||||
let ret_val = nu_prompt as &dyn Prompt;
|
||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
ret_val
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
create_menus,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||
ShellError, Span, Value,
|
||||
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||
@ -98,19 +97,18 @@ pub(crate) fn add_menus(
|
||||
{
|
||||
let (block, _) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let (output, _) = parse(
|
||||
let output = parse(
|
||||
&mut working_set,
|
||||
Some(name), // format!("entry #{}", entry_num)
|
||||
definition.as_bytes(),
|
||||
true,
|
||||
&[],
|
||||
);
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let mut temp_stack = Stack::new();
|
||||
let input = Value::nothing(Span::test_data()).into_pipeline_data();
|
||||
let input = PipelineData::Empty;
|
||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||
|
||||
if let PipelineData::Value(value, None) = res {
|
||||
@ -627,9 +625,12 @@ fn add_parsed_keybinding(
|
||||
"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,
|
||||
"shift_alt" | "alt_shift" => KeyModifiers::SHIFT | KeyModifiers::ALT,
|
||||
"control_shift" | "shift_control" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
||||
"control_alt" | "alt_control" => KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
"control_alt_shift" | "control_shift_alt" => {
|
||||
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||
@ -652,14 +653,15 @@ fn add_parsed_keybinding(
|
||||
let pos1 = char_iter.next();
|
||||
let pos2 = char_iter.next();
|
||||
|
||||
let char = match (pos1, pos2) {
|
||||
(Some(char), None) => Ok(char),
|
||||
_ => Err(ShellError::UnsupportedConfigValue(
|
||||
let char = if let (Some(char), None) = (pos1, pos2) {
|
||||
char
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"char_<CHAR: unicode codepoint>".to_string(),
|
||||
c.to_string(),
|
||||
keybinding.keycode.span()?,
|
||||
)),
|
||||
}?;
|
||||
));
|
||||
};
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
@ -680,10 +682,10 @@ fn add_parsed_keybinding(
|
||||
let fn_num: u8 = c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| matches!(num, 1..=12))
|
||||
.filter(|num| matches!(num, 1..=20))
|
||||
.ok_or(ShellError::UnsupportedConfigValue(
|
||||
"(f1|f2|...|f12)".to_string(),
|
||||
format!("unknown function key: {}", c),
|
||||
"(f1|f2|...|f20)".to_string(),
|
||||
format!("unknown function key: {c}"),
|
||||
keybinding.keycode.span()?,
|
||||
))?;
|
||||
KeyCode::F(fn_num)
|
||||
@ -990,7 +992,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_send_event() {
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::string("Enter", Span::test_data())];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
@ -1010,7 +1012,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_edit_event() {
|
||||
let cols = vec!["edit".to_string()];
|
||||
let vals = vec![Value::string("Clear", Span::test_data())];
|
||||
let vals = vec![Value::test_string("Clear")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
@ -1034,8 +1036,8 @@ mod test {
|
||||
fn test_send_menu() {
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::string("Menu", Span::test_data()),
|
||||
Value::string("history_menu", Span::test_data()),
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let span = Span::test_data();
|
||||
@ -1061,8 +1063,8 @@ mod test {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::string("Menu", Span::test_data()),
|
||||
Value::string("history_menu", Span::test_data()),
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
@ -1073,7 +1075,7 @@ mod test {
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::string("Enter", Span::test_data())];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
@ -1114,8 +1116,8 @@ mod test {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::string("Menu", Span::test_data()),
|
||||
Value::string("history_menu", Span::test_data()),
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
@ -1126,7 +1128,7 @@ mod test {
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::string("Enter", Span::test_data())];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
@ -1154,7 +1156,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let cols = vec!["not_exist".to_string()];
|
||||
let vals = vec![Value::string("Enter", Span::test_data())];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||
|
@ -2,21 +2,25 @@ use crate::{
|
||||
completions::NuCompleter,
|
||||
prompt_update,
|
||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||
util::eval_source,
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use log::{info, trace, warn};
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use log::{trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
|
||||
use nu_command::hook::eval_hook;
|
||||
use nu_command::util::get_guaranteed_cwd;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||
Spanned, Type, Value, VarId,
|
||||
config::NuCursorShape,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
format_duration, report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError,
|
||||
Span, Spanned, Value,
|
||||
};
|
||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use nu_utils::utils::perf;
|
||||
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
sync::atomic::Ordering,
|
||||
@ -39,8 +43,11 @@ pub fn evaluate_repl(
|
||||
stack: &mut Stack,
|
||||
nushell_path: &str,
|
||||
prerun_command: Option<Spanned<String>>,
|
||||
entire_start_time: Instant,
|
||||
) -> Result<()> {
|
||||
use nu_command::hook;
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
let use_color = engine_state.get_config().use_ansi_coloring;
|
||||
|
||||
// Guard against invocation without a connected terminal.
|
||||
// reedline / crossterm event polling will fail without a connected tty
|
||||
@ -56,18 +63,20 @@ pub fn evaluate_repl(
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
|
||||
info!(
|
||||
"translate environment vars {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
// 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);
|
||||
}
|
||||
perf(
|
||||
"translate env vars",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
// seed env vars
|
||||
stack.add_env_var(
|
||||
@ -77,33 +86,32 @@ pub fn evaluate_repl(
|
||||
|
||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
|
||||
info!(
|
||||
"load config initially {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
let mut start_time = std::time::Instant::now();
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
let hist_sesh = match line_editor.get_history_session_id() {
|
||||
Some(id) => i64::from(id),
|
||||
None => 0,
|
||||
};
|
||||
let hist_sesh = line_editor
|
||||
.get_history_session_id()
|
||||
.map(i64::from)
|
||||
.unwrap_or(0);
|
||||
engine_state.history_session_id = hist_sesh;
|
||||
perf(
|
||||
"setup reedline",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
);
|
||||
if let Some(history_path) = history_path.as_deref() {
|
||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::PlainText => Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
@ -118,7 +126,16 @@ pub fn evaluate_repl(
|
||||
};
|
||||
line_editor = line_editor.with_history(history);
|
||||
};
|
||||
perf(
|
||||
"setup history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
let show_banner = config.show_banner;
|
||||
@ -126,61 +143,113 @@ pub fn evaluate_repl(
|
||||
if show_banner {
|
||||
let banner = get_banner(engine_state, stack);
|
||||
if use_ansi {
|
||||
println!("{}", banner);
|
||||
println!("{banner}");
|
||||
} else {
|
||||
println!("{}", nu_utils::strip_ansi_string_likely(banner));
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"get sysinfo/show banner",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.item.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
&format!("entry #{entry_num}"),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
||||
}
|
||||
|
||||
loop {
|
||||
info!(
|
||||
"load config each loop {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
let loop_start_time = std::time::Instant::now();
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
perf(
|
||||
"merge env",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
perf(
|
||||
"reset ctrlc",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Reset the SIGQUIT handler
|
||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
||||
sig_quit.store(false, Ordering::SeqCst);
|
||||
}
|
||||
perf(
|
||||
"reset sig_quit",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
|
||||
// Find the configured cursor shapes for each mode
|
||||
let cursor_config = CursorConfig {
|
||||
vi_insert: Some(map_nucursorshape_to_cursorshape(
|
||||
config.cursor_shape_vi_insert,
|
||||
)),
|
||||
vi_normal: Some(map_nucursorshape_to_cursorshape(
|
||||
config.cursor_shape_vi_normal,
|
||||
)),
|
||||
emacs: Some(map_nucursorshape_to_cursorshape(config.cursor_shape_emacs)),
|
||||
};
|
||||
perf(
|
||||
"get config/cursor config",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = line_editor
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_state.clone(),
|
||||
engine_state: engine_reference.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
engine_state: engine_reference.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_reference.clone(),
|
||||
@ -188,10 +257,20 @@ pub fn evaluate_repl(
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring);
|
||||
.with_ansi_colors(config.use_ansi_coloring)
|
||||
.with_cursor_config(cursor_config);
|
||||
perf(
|
||||
"reedline builder",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = if config.use_ansi_coloring {
|
||||
line_editor.with_hinter(Box::new({
|
||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||
@ -201,16 +280,31 @@ pub fn evaluate_repl(
|
||||
} else {
|
||||
line_editor.disable_hints()
|
||||
};
|
||||
perf(
|
||||
"reedline coloring/style_computer",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
line_editor = match add_menus(line_editor, engine_reference, stack, config) {
|
||||
Ok(line_editor) => line_editor,
|
||||
Err(e) => {
|
||||
start_time = std::time::Instant::now();
|
||||
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
Reedline::create()
|
||||
}
|
||||
};
|
||||
});
|
||||
perf(
|
||||
"reedline menus",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let buffer_editor = if !config.buffer_editor.is_empty() {
|
||||
Some(config.buffer_editor.clone())
|
||||
} else {
|
||||
@ -231,17 +325,31 @@ pub fn evaluate_repl(
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
perf(
|
||||
"reedline buffer_editor",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
if config.sync_history_on_enter {
|
||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"sync_history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = match create_keybindings(config) {
|
||||
Ok(keybindings) => match keybindings {
|
||||
@ -263,9 +371,16 @@ pub fn evaluate_repl(
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
perf(
|
||||
"keybindings",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
@ -273,28 +388,58 @@ pub fn evaluate_repl(
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let config = engine_state.get_config();
|
||||
start_time = std::time::Instant::now();
|
||||
let config = &engine_state.get_config().clone();
|
||||
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
||||
perf(
|
||||
"update_prompt",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
entry_num += 1;
|
||||
|
||||
info!(
|
||||
"finished setup, starting repl {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
if entry_num == 1 {
|
||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||
if show_banner {
|
||||
println!(
|
||||
"Startup Time: {}",
|
||||
format_duration(engine_state.get_startup_time())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let input = line_editor.read_line(prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
@ -316,20 +461,29 @@ pub fn evaluate_repl(
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
engine_state
|
||||
// Right before we start running the code the user gave us, fire the `pre_execution`
|
||||
// hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||
let mut repl_buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
.replace(line_editor.current_buffer_contents().to_string());
|
||||
.expect("repl buffer state mutex");
|
||||
*repl_buffer = s.to_string();
|
||||
drop(repl_buffer);
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
let mut repl_buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex");
|
||||
*repl_buffer = line_editor.current_buffer_contents().to_string();
|
||||
drop(repl_buffer);
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
}
|
||||
@ -421,8 +575,9 @@ pub fn evaluate_repl(
|
||||
engine_state,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
&format!("entry #{entry_num}"),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
let cmd_duration = start_time.elapsed();
|
||||
@ -479,28 +634,28 @@ pub fn evaluate_repl(
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
||||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
}
|
||||
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
let mut repl_buffer = engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
while let Some(op) = ops.pop_front() {
|
||||
match op {
|
||||
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
||||
EditCommand::MoveToEnd,
|
||||
EditCommand::InsertString(s),
|
||||
]),
|
||||
ReplOperation::Insert(s) => {
|
||||
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
||||
}
|
||||
ReplOperation::Replace(s) => line_editor
|
||||
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
||||
}
|
||||
}
|
||||
.expect("repl buffer state mutex");
|
||||
let mut repl_cursor_pos = engine_state
|
||||
.repl_cursor_pos
|
||||
.lock()
|
||||
.expect("repl cursor pos mutex");
|
||||
line_editor.run_edit_commands(&[
|
||||
EditCommand::Clear,
|
||||
EditCommand::InsertString(repl_buffer.to_string()),
|
||||
EditCommand::MoveToPosition(*repl_cursor_pos),
|
||||
]);
|
||||
*repl_buffer = "".to_string();
|
||||
drop(repl_buffer);
|
||||
*repl_cursor_pos = 0;
|
||||
drop(repl_cursor_pos);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
@ -519,7 +674,7 @@ pub fn evaluate_repl(
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
eprintln!("Error: {:?}", err);
|
||||
eprintln!("Error: {err:?}");
|
||||
// TODO: Identify possible error cases where a hard failure is preferable
|
||||
// Ignoring and reporting could hide bigger problems
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
@ -530,11 +685,36 @@ pub fn evaluate_repl(
|
||||
}
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"processing line editor input",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
perf(
|
||||
"finished repl loop",
|
||||
loop_start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> SetCursorStyle {
|
||||
match shape {
|
||||
NuCursorShape::Block => SetCursorStyle::SteadyBlock,
|
||||
NuCursorShape::UnderScore => SetCursorStyle::DefaultUserShape,
|
||||
NuCursorShape::Line => SetCursorStyle::BlinkingBar,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
let age = match eval_string_with_input(
|
||||
engine_state,
|
||||
@ -554,19 +734,12 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
|
||||
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
|
||||
Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
||||
Our {}Documentation{} is located at {}http://nushell.sh{}
|
||||
Our {}Documentation{} is located at {}https://nushell.sh{}
|
||||
{}Tweet{} us at {}@nu_shell{}
|
||||
Learn how to remove this at: {}https://nushell.sh/book/configuration.html#remove-welcome-message{}
|
||||
|
||||
It's been this long since {}Nushell{}'s first commit:
|
||||
{}
|
||||
|
||||
{}You can disable this banner using the {}config nu{}{} command
|
||||
to modify the config.nu file and setting show_banner to false.
|
||||
|
||||
let-env config = {{
|
||||
show_banner: false
|
||||
...
|
||||
}}{}
|
||||
{}{}
|
||||
"#,
|
||||
"\x1b[32m", //start line 1 green
|
||||
"\x1b[32m", //start line 2
|
||||
@ -595,13 +768,11 @@ let-env config = {{
|
||||
"\x1b[0m", //after Tweet
|
||||
"\x1b[1;36m", //before @nu_shell cyan_bold
|
||||
"\x1b[0m", //after @nu_shell
|
||||
"\x1b[32m", //before Welcome Message
|
||||
"\x1b[0m", //after Welcome Message
|
||||
"\x1b[32m", //before Nushell
|
||||
"\x1b[0m", //after Nushell
|
||||
age,
|
||||
"\x1b[2;37m", //before banner disable dim white
|
||||
"\x1b[2;36m", //before config nu dim cyan
|
||||
"\x1b[0m", //after config nu
|
||||
"\x1b[2;37m", //after config nu dim white
|
||||
"\x1b[0m", //after banner disable
|
||||
);
|
||||
|
||||
@ -619,7 +790,7 @@ pub fn eval_string_with_input(
|
||||
) -> Result<Value, ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, _) = parse(&mut working_set, None, source.as_bytes(), false, &[]);
|
||||
let output = parse(&mut working_set, None, source.as_bytes(), false);
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
@ -639,7 +810,7 @@ pub fn eval_string_with_input(
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.map(|x| x.into_value(Span::test_data()))
|
||||
.map(|x| x.into_value(Span::unknown()))
|
||||
}
|
||||
|
||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
@ -650,354 +821,16 @@ pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) ->
|
||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
}
|
||||
|
||||
pub fn eval_env_change_hook(
|
||||
env_change_hook: Option<Value>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(hook) = env_change_hook {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols: env_names,
|
||||
vals: hook_values,
|
||||
..
|
||||
} => {
|
||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let after = stack
|
||||
.get_env_var(engine_state, env_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
if before != after {
|
||||
eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
None,
|
||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||
hook_value,
|
||||
)?;
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_name.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::TypeMismatch(
|
||||
"record for the 'env_change' hook".to_string(),
|
||||
x.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_hook(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: Option<PipelineData>,
|
||||
arguments: Vec<(String, Value)>,
|
||||
value: &Value,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value_span = value.span()?;
|
||||
|
||||
// Hooks can optionally be a record in this form:
|
||||
// {
|
||||
// condition: {|before, after| ... } # block that evaluates to true/false
|
||||
// code: # block or a string
|
||||
// }
|
||||
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
||||
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||
let condition_path = PathMember::String {
|
||||
val: "condition".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
let mut output = PipelineData::empty();
|
||||
|
||||
let code_path = PathMember::String {
|
||||
val: "code".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
|
||||
}
|
||||
}
|
||||
Value::Record { .. } => {
|
||||
let do_run_hook =
|
||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
||||
match condition {
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
}
|
||||
| Value::Closure {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
match run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
None,
|
||||
arguments.clone(),
|
||||
block_span,
|
||||
) {
|
||||
Ok(pipeline_data) => {
|
||||
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
||||
pipeline_data
|
||||
{
|
||||
val
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"boolean output".to_string(),
|
||||
"other PipelineData variant".to_string(),
|
||||
block_span,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always run the hook
|
||||
true
|
||||
};
|
||||
|
||||
if do_run_hook {
|
||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
||||
Value::String {
|
||||
val,
|
||||
span: source_span,
|
||||
} => {
|
||||
let (block, delta, vars) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||
|
||||
for (name, val) in arguments {
|
||||
let var_id = working_set.add_variable(
|
||||
name.as_bytes().to_vec(),
|
||||
val.span()?,
|
||||
Type::Any,
|
||||
false,
|
||||
);
|
||||
|
||||
vars.push((var_id, val));
|
||||
}
|
||||
|
||||
let (output, err) =
|
||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"valid source code".into(),
|
||||
"source code with syntax errors".into(),
|
||||
source_span,
|
||||
));
|
||||
}
|
||||
|
||||
(output, working_set.render(), vars)
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
let input = PipelineData::empty();
|
||||
|
||||
let var_ids: Vec<VarId> = vars
|
||||
.into_iter()
|
||||
.map(|(var_id, val)| {
|
||||
stack.add_var(var_id, val);
|
||||
var_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
output = pipeline_data;
|
||||
}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
for var_id in var_ids.iter() {
|
||||
stack.vars.remove(var_id);
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
input,
|
||||
arguments,
|
||||
block_span,
|
||||
)?;
|
||||
}
|
||||
Value::Closure {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
input,
|
||||
arguments,
|
||||
block_span,
|
||||
)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block or string".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
output = run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
*block_id,
|
||||
input,
|
||||
arguments,
|
||||
*block_span,
|
||||
)?;
|
||||
}
|
||||
Value::Closure {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
output = run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
*block_id,
|
||||
input,
|
||||
arguments,
|
||||
*block_span,
|
||||
)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block, record, or list of records".into(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
optional_input: Option<PipelineData>,
|
||||
arguments: Vec<(String, Value)>,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
if let Some(arg) = arguments.get(idx) {
|
||||
callee_stack.add_var(*var_id, arg.1.clone())
|
||||
} else {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"This hook block has too many parameters".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)
|
||||
{
|
||||
Ok(pipeline_data) => {
|
||||
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// If all went fine, preserve the environment of the called block
|
||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||
|
||||
// remove env vars that are present in the caller but not in the callee
|
||||
// (the callee hid them)
|
||||
for var in caller_env_vars.iter() {
|
||||
if !callee_stack.has_env_var(engine_state, var) {
|
||||
stack.remove_env_var(engine_state, var);
|
||||
}
|
||||
}
|
||||
|
||||
// add new env vars from callee to caller
|
||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||
stack.add_env_var(var, value);
|
||||
}
|
||||
Ok(pipeline_data)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
match io::stdout().write_all(seq.as_bytes()) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error writing ansi sequence".into(),
|
||||
err.to_string(),
|
||||
e.to_string(),
|
||||
Some(Span::unknown()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
)
|
||||
})?;
|
||||
io::stdout().flush().map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error flushing stdio".into(),
|
||||
|
@ -6,9 +6,10 @@ use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use nu_protocol::{Config, Span};
|
||||
use reedline::{Highlighter, StyledText};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuHighlighter {
|
||||
pub engine_state: EngineState,
|
||||
pub engine_state: Arc<EngineState>,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
@ -17,10 +18,7 @@ impl Highlighter for NuHighlighter {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let block = {
|
||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
block
|
||||
};
|
||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||
let (shapes, global_span_offset) = {
|
||||
let shapes = flatten_block(&working_set, &block);
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
@ -78,29 +76,28 @@ impl Highlighter for NuHighlighter {
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! add_colored_token {
|
||||
($shape:expr, $text:expr) => {
|
||||
output.push((get_shape_color($shape.to_string(), &self.config), $text))
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
output.push((get_shape_color(shape.to_string(), &self.config), text));
|
||||
};
|
||||
}
|
||||
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::External => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::String => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
@ -114,17 +111,23 @@ impl Highlighter for NuHighlighter {
|
||||
FlatShape::Block => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Closure => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
|
||||
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::And => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Or => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
}
|
||||
FlatShape::Flag => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::And => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Or => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
@ -233,7 +236,8 @@ fn find_matching_block_end_in_block(
|
||||
PipelineElement::Expression(_, e)
|
||||
| PipelineElement::Redirection(_, _, e)
|
||||
| PipelineElement::And(_, e)
|
||||
| PipelineElement::Or(_, e) => {
|
||||
| PipelineElement::Or(_, e)
|
||||
| PipelineElement::SeparateRedirection { out: (_, e), .. } => {
|
||||
if e.span.contains(global_cursor_offset) {
|
||||
if let Some(pos) = find_matching_block_end_in_expr(
|
||||
line,
|
||||
@ -306,6 +310,8 @@ fn find_matching_block_end_in_expr(
|
||||
Expr::ImportPattern(_) => None,
|
||||
Expr::Overlay(_) => None,
|
||||
Expr::Signature(_) => None,
|
||||
Expr::MatchPattern(_) => None,
|
||||
Expr::MatchBlock(_) => None,
|
||||
Expr::Nothing => None,
|
||||
Expr::Garbage => None,
|
||||
|
||||
@ -352,6 +358,7 @@ fn find_matching_block_end_in_expr(
|
||||
let opt_expr = match arg {
|
||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||
};
|
||||
|
||||
if let Some(inner_expr) = opt_expr {
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::repl::eval_hook;
|
||||
use nu_engine::eval_block;
|
||||
use nu_command::hook::eval_hook;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::CliError;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_protocol::{report_error, report_error_new};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use std::path::{Path, PathBuf};
|
||||
use nu_utils::utils::perf;
|
||||
use std::path::Path;
|
||||
|
||||
// This will collect environment variables from std::env and adds them to a stack.
|
||||
//
|
||||
@ -43,7 +44,7 @@ fn gather_env_vars(
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
format!("Environment variable was not captured: {}", env_str),
|
||||
format!("Environment variable was not captured: {env_str}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(msg.into()),
|
||||
@ -79,8 +80,7 @@ fn gather_env_vars(
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!(
|
||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||
init_cwd
|
||||
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
|
||||
)),
|
||||
Vec::new(),
|
||||
),
|
||||
@ -113,7 +113,8 @@ fn gather_env_vars(
|
||||
span,
|
||||
}) = parts.get(0)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let bytes = working_set.get_span_contents(*span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
@ -125,9 +126,12 @@ fn gather_env_vars(
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
let (bytes, err) = unescape_unquote_string(bytes, *span);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err);
|
||||
}
|
||||
|
||||
if parse_error.is_some() {
|
||||
if working_set.parse_errors.first().is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
@ -153,7 +157,8 @@ fn gather_env_vars(
|
||||
span,
|
||||
}) = parts.get(2)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let bytes = working_set.get_span_contents(*span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
@ -165,9 +170,12 @@ fn gather_env_vars(
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
let (bytes, err) = unescape_unquote_string(bytes, *span);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err);
|
||||
}
|
||||
|
||||
if parse_error.is_some() {
|
||||
if working_set.parse_errors.first().is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
@ -203,19 +211,21 @@ pub fn eval_source(
|
||||
source: &[u8],
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
allow_return: bool,
|
||||
) -> bool {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, err) = parse(
|
||||
let output = parse(
|
||||
&mut working_set,
|
||||
Some(fname), // format!("entry #{}", entry_num)
|
||||
source,
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
if let Some(err) = err {
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error(&working_set, &err);
|
||||
report_error(&working_set, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -228,7 +238,13 @@ pub fn eval_source(
|
||||
return false;
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
let b = if allow_return {
|
||||
eval_block_with_early_return(engine_state, stack, &block, input, false, false)
|
||||
} else {
|
||||
eval_block(engine_state, stack, &block, input, false, false)
|
||||
};
|
||||
|
||||
match b {
|
||||
Ok(pipeline_data) => {
|
||||
let config = engine_state.get_config();
|
||||
let result;
|
||||
@ -282,6 +298,14 @@ pub fn eval_source(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
perf(
|
||||
&format!("eval_source {}", &fname),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
@ -293,51 +317,6 @@ fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn report_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = nu_utils::enable_vt_processing();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error_new(
|
||||
engine_state: &EngineState,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, error);
|
||||
}
|
||||
|
||||
pub fn get_init_cwd() -> PathBuf {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
Err(_) => match std::env::var("PWD") {
|
||||
Ok(cwd) => PathBuf::from(cwd),
|
||||
Err(_) => match nu_path::home_dir() {
|
||||
Some(cwd) => cwd,
|
||||
None => PathBuf::new(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -1,17 +1,24 @@
|
||||
use nu_parser::{parse, ParseError};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
ParseError,
|
||||
};
|
||||
use reedline::{ValidationResult, Validator};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuValidator {
|
||||
pub engine_state: EngineState,
|
||||
pub engine_state: Arc<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, &[]);
|
||||
parse(&mut working_set, None, line.as_bytes(), false);
|
||||
|
||||
if matches!(err, Some(ParseError::UnexpectedEof(..))) {
|
||||
if matches!(
|
||||
working_set.parse_errors.first(),
|
||||
Some(ParseError::UnexpectedEof(..))
|
||||
) {
|
||||
ValidationResult::Incomplete
|
||||
} else {
|
||||
ValidationResult::Complete
|
||||
|
@ -34,6 +34,26 @@ fn completer_strings() -> NuCompleter {
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn extern_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
def animals [] { [ "cat", "dog", "eel" ] }
|
||||
extern spam [
|
||||
animal: string@animals
|
||||
--foo (-f): string@animals
|
||||
-b: string@animals
|
||||
]
|
||||
"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_varialblecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
@ -89,7 +109,7 @@ fn dotnu_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test source completion
|
||||
@ -149,20 +169,20 @@ fn file_completions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cp {}", dir_str);
|
||||
let target_dir = format!("cp {dir_str}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("another")),
|
||||
file(dir.join("custom_completion.nu")),
|
||||
file(dir.join("nushell")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
file(dir.join("custom_completion.nu")),
|
||||
file(dir.join(".hidden_file")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
@ -192,21 +212,21 @@ fn command_ls_with_filecompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -224,21 +244,21 @@ fn command_open_with_filecompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -257,21 +277,21 @@ fn command_rm_with_globcompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -290,21 +310,21 @@ fn command_cp_with_globcompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -323,21 +343,21 @@ fn command_save_with_filecompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -356,21 +376,21 @@ fn command_touch_with_filecompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -389,21 +409,21 @@ fn command_watch_with_filecompletion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -424,6 +444,7 @@ fn file_completion_quoted() {
|
||||
"`te st.txt`".to_string(),
|
||||
"`te#st.txt`".to_string(),
|
||||
"`te'st.txt`".to_string(),
|
||||
"`te(st).txt`".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
@ -434,12 +455,12 @@ fn flag_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(14, suggestions.len());
|
||||
assert_eq!(16, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
@ -448,6 +469,7 @@ fn flag_completions() {
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--mime-type".into(),
|
||||
"--short-names".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
@ -455,6 +477,7 @@ fn flag_completions() {
|
||||
"-f".into(),
|
||||
"-h".into(),
|
||||
"-l".into(),
|
||||
"-m".into(),
|
||||
"-s".into(),
|
||||
];
|
||||
|
||||
@ -467,18 +490,18 @@ fn folder_with_directorycompletions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {}", dir_str);
|
||||
let target_dir = format!("cd {dir_str}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("another")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
@ -495,23 +518,28 @@ fn variables_completions() {
|
||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(9, suggestions.len());
|
||||
assert_eq!(14, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"config-path".into(),
|
||||
"current-exe".into(),
|
||||
"default-config-dir".into(),
|
||||
"env-path".into(),
|
||||
"history-path".into(),
|
||||
"home-path".into(),
|
||||
"is-interactive".into(),
|
||||
"is-login".into(),
|
||||
"loginshell-path".into(),
|
||||
"os-info".into(),
|
||||
"pid".into(),
|
||||
"scope".into(),
|
||||
"startup-time".into(),
|
||||
"temp-path".into(),
|
||||
];
|
||||
|
||||
@ -551,9 +579,12 @@ fn variables_completions() {
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
assert_eq!(3, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
||||
#[cfg(windows)]
|
||||
let expected: Vec<String> = vec!["PWD".into(), "Path".into(), "TEST".into()];
|
||||
#[cfg(not(windows))]
|
||||
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
@ -634,8 +665,8 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||
let (_, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
|
||||
assert!(err.is_none());
|
||||
let block = parse(&mut working_set, None, block.as_bytes(), false);
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
@ -652,7 +683,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||
config.external_completer = Some(latest_block_id);
|
||||
engine_state.set_config(&config);
|
||||
|
||||
// Instatiate a new completer
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||
|
||||
completer.complete(input, input.len())
|
||||
@ -669,21 +700,21 @@ fn unknown_command_completion() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
@ -729,24 +760,113 @@ fn filecompletions_triggers_after_cursor() {
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam ", 5);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7648() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Issue #7648
|
||||
// Nushell crashes when an alias name is shorter than the alias command
|
||||
// and the alias command is a external command
|
||||
// This happens because of offset is not correct.
|
||||
// This crashes before PR #7779
|
||||
let _suggestions = completer.complete("e", 1);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7754() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Issue #7754
|
||||
// Nushell crashes when an alias name is shorter than the alias command
|
||||
// and the alias command contains pipes.
|
||||
// This crashes before PR #7756
|
||||
let _suggestions = completer.complete("ll -a | c", 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_path_env_var_8003() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, _) = new_engine();
|
||||
// Get the path env var in a platform agnostic way
|
||||
let the_path = engine.get_path_env_var();
|
||||
// Make sure it's not empty
|
||||
assert!(the_path.is_some());
|
||||
}
|
||||
|
@ -43,6 +43,22 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
#[cfg(windows)]
|
||||
stack.add_env_var(
|
||||
"Path".to_string(),
|
||||
Value::String {
|
||||
val: "c:\\some\\path;c:\\some\\other\\path".to_string(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
#[cfg(not(windows))]
|
||||
stack.add_env_var(
|
||||
"PATH".to_string(),
|
||||
Value::String {
|
||||
val: "/some/path:/some/other/path".to_string(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
@ -130,9 +146,9 @@ pub fn merge_input(
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
||||
let block = parse(&mut working_set, None, input, false);
|
||||
|
||||
assert!(err.is_none());
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
32
crates/nu-cmd-lang/Cargo.toml
Normal file
32
crates/nu-cmd-lang/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
build = "build.rs"
|
||||
description = "Nushell's core language commands"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.79.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.79.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.79.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.79.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.79.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.79.0" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
|
||||
fancy-regex = "0.11.0"
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.14"
|
||||
shadow-rs = { version = "0.21.0", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.21.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.79.0" }
|
21
crates/nu-cmd-lang/LICENSE
Normal file
21
crates/nu-cmd-lang/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2023 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -4,7 +4,7 @@ fn main() -> shadow_rs::SdResult<()> {
|
||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||
let hash = get_git_hash().unwrap_or_default();
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={hash}");
|
||||
|
||||
shadow_rs::new()
|
||||
}
|
61
crates/nu-cmd-lang/src/core_commands/alias.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/alias.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Alias;
|
||||
|
||||
impl Command for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command (with optional flags) to a new name."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["abbr", "aka", "fn", "func", "function"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Alias ll to ls -l",
|
||||
example: "alias ll = ls -l",
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
}]
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ impl Command for Break {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Break a loop"
|
||||
"Break a loop."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -20,22 +20,13 @@ impl Command for Break {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Err(ShellError::Break(call.head))
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use nu_engine::{eval_block, redirect_env, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -39,7 +40,7 @@ impl Command for Collect {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let capture_block: Closure = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||
@ -71,10 +72,10 @@ impl Command for Collect {
|
||||
// for when we support `data | let x = $in;`
|
||||
// remove the variables added earlier
|
||||
for var_id in capture_block.captures.keys() {
|
||||
stack_captures.vars.remove(var_id);
|
||||
stack_captures.remove_var(*var_id);
|
||||
}
|
||||
if let Some(u) = saved_positional {
|
||||
stack_captures.vars.remove(&u);
|
||||
stack_captures.remove_var(u);
|
||||
}
|
||||
// add any new variables to the stack
|
||||
stack.vars.extend(stack_captures.vars);
|
104
crates/nu-cmd-lang/src/core_commands/const_.rs
Normal file
104
crates/nu-cmd-lang/src/core_commands/const_.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Const;
|
||||
|
||||
impl Command for Const {
|
||||
fn name(&self) -> &str {
|
||||
"const"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a parse-time constant."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("const")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||
"equals sign followed by constant value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["set", "let"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
.as_var()
|
||||
.expect("internal error: missing variable");
|
||||
|
||||
if let Some(constval) = engine_state.find_constant(var_id, &[]) {
|
||||
// Instead of creating a second copy of the value in the stack, we could change
|
||||
// stack.get_var() to check engine_state.find_constant().
|
||||
stack.add_var(var_id, constval.clone());
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
} else {
|
||||
Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Missing Constant".to_string(),
|
||||
label: "constant not added by the parser".to_string(),
|
||||
span: call.head,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a new parse-time constant.",
|
||||
example: "const x = 10",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create a composite constant value",
|
||||
example: "const x = { a: 10, b: 20 }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Const {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_type() {
|
||||
assert!(matches!(Const.command_type(), CommandType::Keyword));
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ impl Command for Continue {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Continue a loop from the next iteration"
|
||||
"Continue a loop from the next iteration."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -20,22 +20,13 @@ impl Command for Continue {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Err(ShellError::Continue(call.head))
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Def;
|
||||
@ -11,7 +13,7 @@ impl Command for Def {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command"
|
||||
"Define a custom command."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -38,7 +40,7 @@ impl Command for Def {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DefEnv;
|
||||
@ -11,7 +13,7 @@ impl Command for DefEnv {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command, which participates in the caller environment"
|
||||
"Define a custom command, which participates in the caller environment."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -26,32 +28,7 @@ impl Command for DefEnv {
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
|
||||
Because of this, the following doesn't work:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
if $arg != "" {
|
||||
cd $arg
|
||||
} else {
|
||||
cd $fall_back_path
|
||||
}
|
||||
}
|
||||
|
||||
Instead, you have to use cd in the top level scope:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
let path = if $arg != "" {
|
||||
$arg
|
||||
} else {
|
||||
$fall_back_path
|
||||
}
|
||||
cd $path
|
||||
}"#
|
||||
"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -64,7 +41,7 @@ def-env cd_with_fallback [arg = ""] {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
@ -72,7 +49,7 @@ def-env cd_with_fallback [arg = ""] {
|
||||
vec![Example {
|
||||
description: "Set environment variable by call a custom command",
|
||||
example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#,
|
||||
result: Some(Value::string("BAZ", Span::test_data())),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
}]
|
||||
}
|
||||
}
|
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Describe;
|
||||
|
||||
impl Command for Describe {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Describe the type and structure of the value(s) piped in."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("describe")
|
||||
.input_output_types(vec![(Type::Any, Type::String)])
|
||||
.switch(
|
||||
"no-collect",
|
||||
"do not collect streams of structured data",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let no_collect: bool = call.has_flag("no-collect");
|
||||
|
||||
let description = match input {
|
||||
PipelineData::ExternalStream { .. } => "raw input".into(),
|
||||
PipelineData::ListStream(_, _) => {
|
||||
if no_collect {
|
||||
"stream".into()
|
||||
} else {
|
||||
let value = input.into_value(head);
|
||||
let base_description = match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
};
|
||||
|
||||
format!("{base_description} (stream)")
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = input.into_value(head);
|
||||
match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::String {
|
||||
val: description,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Describe the type of a string",
|
||||
example: "'hello' | describe",
|
||||
result: Some(Value::test_string("string")),
|
||||
},
|
||||
/*
|
||||
Example {
|
||||
description: "Describe a stream of data, collecting it first",
|
||||
example: "[1 2 3] | each {|i| $i} | describe",
|
||||
result: Some(Value::test_string("list<int> (stream)")),
|
||||
},
|
||||
Example {
|
||||
description: "Describe the input but do not collect streams",
|
||||
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
|
||||
result: Some(Value::test_string("stream")),
|
||||
},
|
||||
*/
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["type", "typeof", "info", "structure"]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Describe;
|
||||
use crate::test_examples;
|
||||
test_examples(Describe {})
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
use std::thread;
|
||||
|
||||
use nu_engine::{eval_block_with_early_return, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,12 +17,16 @@ impl Command for Do {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a closure, providing it with the pipeline input"
|
||||
"Run a closure, providing it with the pipeline input."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("do")
|
||||
.required("closure", SyntaxShape::Any, "the closure to run")
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Closure(None), SyntaxShape::Any]),
|
||||
"the closure to run",
|
||||
)
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
@ -106,7 +113,7 @@ impl Command for Do {
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
capture_errors || ignore_shell_errors || ignore_program_errors,
|
||||
call.redirect_stdout,
|
||||
);
|
||||
|
||||
match result {
|
||||
@ -118,6 +125,59 @@ impl Command for Do {
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
}) if capture_errors => {
|
||||
// Use a thread to receive stdout message.
|
||||
// Or we may get a deadlock if child process sends out too much bytes to stderr.
|
||||
//
|
||||
// For example: in normal linux system, stderr pipe's limit is 65535 bytes.
|
||||
// if child process sends out 65536 bytes, the process will be hanged because no consumer
|
||||
// consumes the first 65535 bytes
|
||||
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
||||
// stderr messages.
|
||||
let stdout_handler = stdout.map(|stdout_stream| {
|
||||
thread::Builder::new()
|
||||
.name("stderr redirector".to_string())
|
||||
.spawn(move || {
|
||||
let ctrlc = stdout_stream.ctrlc.clone();
|
||||
let span = stdout_stream.span;
|
||||
RawStream::new(
|
||||
Box::new(
|
||||
vec![stdout_stream.into_bytes().map(|s| s.item)].into_iter(),
|
||||
),
|
||||
ctrlc,
|
||||
span,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.expect("Failed to create thread")
|
||||
});
|
||||
|
||||
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
||||
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
||||
let mut stderr_ctrlc = None;
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => {
|
||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
stderr_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let stdout = if let Some(handle) = stdout_handler {
|
||||
match handle.join() {
|
||||
Err(err) => {
|
||||
return Err(ShellError::ExternalCommand {
|
||||
label: "Fail to receive external commands stdout message"
|
||||
.to_string(),
|
||||
help: format!("{err:?}"),
|
||||
span,
|
||||
});
|
||||
}
|
||||
Ok(res) => Some(res),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut exit_code_ctrlc = None;
|
||||
let exit_code: Vec<Value> = match exit_code {
|
||||
None => vec![],
|
||||
@ -128,22 +188,22 @@ impl Command for Do {
|
||||
};
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
if *code != 0 {
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => stderr_stream.into_string().map(|s| s.item)?,
|
||||
};
|
||||
|
||||
return Err(ShellError::ExternalCommand(
|
||||
"External command failed".to_string(),
|
||||
stderr_msg,
|
||||
return Err(ShellError::ExternalCommand {
|
||||
label: "External command failed".to_string(),
|
||||
help: stderr_msg,
|
||||
span,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
stderr: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||
stderr_ctrlc,
|
||||
span,
|
||||
None,
|
||||
)),
|
||||
exit_code: Some(ListStream::from_stream(
|
||||
exit_code.into_iter(),
|
||||
exit_code_ctrlc,
|
||||
@ -160,18 +220,35 @@ impl Command for Do {
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
}) if ignore_program_errors => Ok(PipelineData::ExternalStream {
|
||||
}) if ignore_program_errors && !call.redirect_stdout => {
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code: None,
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
}),
|
||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) if ignore_shell_errors => {
|
||||
})
|
||||
}
|
||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
Err(_) if ignore_shell_errors => Ok(PipelineData::empty()),
|
||||
Ok(PipelineData::ListStream(ls, metadata)) if ignore_shell_errors => {
|
||||
// check if there is a `Value::Error` in given list stream first.
|
||||
let mut values = vec![];
|
||||
let ctrlc = ls.ctrlc.clone();
|
||||
for v in ls {
|
||||
if let Value::Error { .. } = v {
|
||||
values.push(Value::nothing(call.head));
|
||||
} else {
|
||||
values.push(v)
|
||||
}
|
||||
}
|
||||
Ok(PipelineData::ListStream(
|
||||
ListStream::from_stream(values.into_iter(), ctrlc),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
r => r,
|
||||
}
|
||||
}
|
212
crates/nu-cmd-lang/src/core_commands/error_make.rs
Normal file
212
crates/nu-cmd-lang/src/core_commands/error_make.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ErrorMake;
|
||||
|
||||
impl Command for ErrorMake {
|
||||
fn name(&self) -> &str {
|
||||
"error make"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("error make")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Error)])
|
||||
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||
.switch(
|
||||
"unspanned",
|
||||
"remove the origin label from the error",
|
||||
Some('u'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create an error."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["panic", "crash", "throw"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
let unspanned = call.has_flag("unspanned");
|
||||
|
||||
let throw_error = if unspanned { None } else { Some(span) };
|
||||
Err(make_error(&arg, throw_error).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a simple custom error",
|
||||
example: r#"error make {msg: "my custom error message"}"#,
|
||||
result: Some(Value::Error {
|
||||
error: Box::new(ShellError::GenericError(
|
||||
"my custom error message".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Create a more complex custom error",
|
||||
example: r#"error make {
|
||||
msg: "my custom error message"
|
||||
label: {
|
||||
text: "my custom label text" # not mandatory unless $.label exists
|
||||
start: 123 # not mandatory unless $.label.end is set
|
||||
end: 456 # not mandatory unless $.label.start is set
|
||||
}
|
||||
}"#,
|
||||
result: Some(Value::Error {
|
||||
error: Box::new(ShellError::GenericError(
|
||||
"my custom error message".to_string(),
|
||||
"my custom label text".to_string(),
|
||||
Some(Span::new(123, 456)),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Create a custom error for a custom command that shows the span of the argument",
|
||||
example: r#"def foo [x] {
|
||||
let span = (metadata $x).span;
|
||||
error make {
|
||||
msg: "this is fishy"
|
||||
label: {
|
||||
text: "fish right here"
|
||||
start: $span.start
|
||||
end: $span.end
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
||||
if let Value::Record { span, .. } = &value {
|
||||
let msg = value.get_data_by_key("msg");
|
||||
let label = value.get_data_by_key("label");
|
||||
|
||||
match (msg, &label) {
|
||||
(Some(Value::String { val: message, .. }), Some(label)) => {
|
||||
let label_start = label.get_data_by_key("start");
|
||||
let label_end = label.get_data_by_key("end");
|
||||
let label_text = label.get_data_by_key("text");
|
||||
|
||||
let label_span = match label.span() {
|
||||
Ok(lspan) => Some(lspan),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
match (label_start, label_end, label_text) {
|
||||
(
|
||||
Some(Value::Int { val: start, .. }),
|
||||
Some(Value::Int { val: end, .. }),
|
||||
Some(Value::String {
|
||||
val: label_text, ..
|
||||
}),
|
||||
) => {
|
||||
if start > end {
|
||||
Some(ShellError::GenericError(
|
||||
"invalid error format.".into(),
|
||||
"`$.label.start` should be smaller than `$.label.end`".into(),
|
||||
label_span,
|
||||
Some(format!("{} > {}", start, end)),
|
||||
Vec::new(),
|
||||
))
|
||||
} else {
|
||||
Some(ShellError::GenericError(
|
||||
message,
|
||||
label_text,
|
||||
Some(Span::new(start as usize, end as usize)),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
(
|
||||
None,
|
||||
None,
|
||||
Some(Value::String {
|
||||
val: label_text, ..
|
||||
}),
|
||||
) => Some(ShellError::GenericError(
|
||||
message,
|
||||
label_text,
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
(_, _, None) => Some(ShellError::GenericError(
|
||||
"Unable to parse error format.".into(),
|
||||
"missing required member `$.label.text`".into(),
|
||||
label_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
(Some(Value::Int { .. }), None, _) => Some(ShellError::GenericError(
|
||||
"Unable to parse error format.".into(),
|
||||
"missing required member `$.label.end`".into(),
|
||||
label_span,
|
||||
Some("required because `$.label.start` is set".to_string()),
|
||||
Vec::new(),
|
||||
)),
|
||||
(None, Some(Value::Int { .. }), _) => Some(ShellError::GenericError(
|
||||
"Unable to parse error format.".into(),
|
||||
"missing required member `$.label.start`".into(),
|
||||
label_span,
|
||||
Some("required because `$.label.end` is set".to_string()),
|
||||
Vec::new(),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
||||
message,
|
||||
"originates from here".to_string(),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
(None, _) => Some(ShellError::GenericError(
|
||||
"Unable to parse error format.".into(),
|
||||
"missing required member `$.msg`".into(),
|
||||
Some(*span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, Span, Type, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -38,7 +38,7 @@ impl Command for ExportCommand {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&ExportCommand.signature(),
|
||||
@ -56,7 +56,7 @@ impl Command for ExportCommand {
|
||||
vec![Example {
|
||||
description: "Export a definition from a module",
|
||||
example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#,
|
||||
result: Some(Value::string("hello", Span::test_data())),
|
||||
result: Some(Value::test_string("hello")),
|
||||
}]
|
||||
}
|
||||
|
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportAlias;
|
||||
|
||||
impl Command for ExportAlias {
|
||||
fn name(&self) -> &str {
|
||||
"export alias"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command (with optional flags) to a new name and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["abbr", "aka", "fn", "func", "function"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Alias ll to ls -l and export it from a module",
|
||||
example: "module spam { export alias ll = ls -l }",
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportDef;
|
||||
@ -11,7 +13,7 @@ impl Command for ExportDef {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command and export it from a module"
|
||||
"Define a custom command and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -38,7 +40,7 @@ impl Command for ExportDef {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
@ -46,7 +48,7 @@ impl Command for ExportDef {
|
||||
vec![Example {
|
||||
description: "Define a custom command in a module and call it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||
result: Some(Value::string("foo", Span::test_data())),
|
||||
result: Some(Value::test_string("foo")),
|
||||
}]
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportDefEnv;
|
||||
@ -11,7 +13,7 @@ impl Command for ExportDefEnv {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command that participates in the environment and export it from a module"
|
||||
"Define a custom command that participates in the environment and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -64,7 +66,7 @@ export def-env cd_with_fallback [arg = ""] {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
@ -72,7 +74,7 @@ export def-env cd_with_fallback [arg = ""] {
|
||||
vec![Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
result: Some(Value::string("BAZ", Span::test_data())),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
}]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportExtern;
|
||||
@ -11,7 +11,7 @@ impl Command for ExportExtern {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define an extern and export it from a module"
|
||||
"Define an extern and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -37,7 +37,7 @@ impl Command for ExportExtern {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportUse;
|
||||
@ -11,13 +13,18 @@ impl Command for ExportUse {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use definitions from a module and export them from this module"
|
||||
"Use definitions from a module and export them from this module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export use")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
||||
.required("module", SyntaxShape::String, "Module or module file")
|
||||
.optional(
|
||||
"members",
|
||||
SyntaxShape::Any,
|
||||
"Which members of the module to import",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -36,7 +43,7 @@ impl Command for ExportUse {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
@ -48,7 +55,7 @@ impl Command for ExportUse {
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::string("foo", Span::test_data())),
|
||||
result: Some(Value::test_string("foo")),
|
||||
}]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Extern;
|
||||
@ -11,7 +11,7 @@ impl Command for Extern {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a signature for an external command"
|
||||
"Define a signature for an external command."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -37,7 +37,7 @@ impl Command for Extern {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use nu_engine::{eval_block, eval_expression, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,11 +14,13 @@ impl Command for For {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Loop over a range"
|
||||
"Loop over a range."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("for")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required(
|
||||
"var_name",
|
||||
SyntaxShape::VarWithOptType,
|
||||
@ -32,7 +34,7 @@ impl Command for For {
|
||||
.required("block", SyntaxShape::Block, "the block to run")
|
||||
.switch(
|
||||
"numbered",
|
||||
"returned a numbered item ($it.index and $it.item)",
|
||||
"return a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
.creates_scope()
|
||||
@ -54,7 +56,7 @@ impl Command for For {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
@ -118,9 +120,11 @@ impl Command for For {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(&engine_state, stack, false, false)?;
|
||||
let exit_code = pipeline.drain_with_exit_code()?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
||||
exit_code,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,9 +164,11 @@ impl Command for For {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(&engine_state, stack, false, false)?;
|
||||
let exit_code = pipeline.drain_with_exit_code()?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
||||
exit_code,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
@ -0,0 +1,260 @@
|
||||
use crate::help_aliases::help_aliases;
|
||||
use crate::help_commands::help_commands;
|
||||
use crate::help_modules::help_modules;
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::{
|
||||
Color::{Red, White},
|
||||
Style,
|
||||
};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
#[derive(Clone)]
|
||||
pub struct Help;
|
||||
|
||||
impl Command for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help")
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of command, alias or module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in command names, usage, and search terms",
|
||||
Some('f'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about different parts of Nushell."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"`help word` searches for "word" in commands, aliases and modules, in that order."#
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
if rest.is_empty() && find.is_none() {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help -h or help help - show available `help` subcommands and examples
|
||||
* help commands - list all available commands
|
||||
* help <name> - display help about a particular command, alias, or module
|
||||
* help --find <text to search> - search through all help commands table
|
||||
|
||||
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||
|
||||
[Examples]
|
||||
|
||||
List the files in the current directory, sorted by size:
|
||||
ls | sort-by size
|
||||
|
||||
Get information about the current system:
|
||||
sys | get host
|
||||
|
||||
Get the processes on your system actively using CPU:
|
||||
ps | where cpu > 0
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
Ok(Value::string(msg, head).into_pipeline_data())
|
||||
} else if find.is_some() {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
let result = help_aliases(engine_state, stack, call);
|
||||
|
||||
let result = if let Err(ShellError::AliasNotFound(_)) = result {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
||||
help_modules(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
if let Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: _,
|
||||
span: _,
|
||||
}) = result
|
||||
{
|
||||
let rest_spans: Vec<Span> = rest.iter().map(|arg| arg.span).collect();
|
||||
Err(ShellError::NotFound {
|
||||
span: span(&rest_spans),
|
||||
})
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show help for single command, alias, or module",
|
||||
example: "help match",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single sub-command, alias, or module",
|
||||
example: "help str lpad",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in command names, usage and search terms",
|
||||
example: "help --find char",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_search_in_table(
|
||||
table: Vec<Value>, // list of records
|
||||
search_string: &str,
|
||||
searched_cols: &[&str],
|
||||
string_style: &Style,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let orig_search_string = search_string;
|
||||
let search_string = search_string.to_lowercase();
|
||||
let mut matches = vec![];
|
||||
|
||||
for record in table {
|
||||
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
|
||||
(cols, vals, span)
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Expected record".to_string(),
|
||||
label: format!("got {}", record.get_type()),
|
||||
span: record.span()?,
|
||||
});
|
||||
};
|
||||
|
||||
let has_match = cols.iter().zip(vals.iter_mut()).fold(
|
||||
Ok(false),
|
||||
|acc: Result<bool, ShellError>, (col, val)| {
|
||||
if searched_cols.contains(&col.as_str()) {
|
||||
if let Value::String { val: s, span } = val {
|
||||
if s.to_lowercase().contains(&search_string) {
|
||||
*val = Value::String {
|
||||
val: highlight_search_string(s, orig_search_string, string_style)?,
|
||||
span: *span,
|
||||
};
|
||||
Ok(true)
|
||||
} else {
|
||||
// column does not contain the searched string
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// ignore non-string values
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// don't search this column
|
||||
acc
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
if has_match {
|
||||
matches.push(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: record_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(matches)
|
||||
}
|
||||
|
||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||
pub fn highlight_search_string(
|
||||
haystack: &str,
|
||||
needle: &str,
|
||||
string_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){needle}");
|
||||
let regex = match Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Could not compile regex".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
// strip haystack to remove existing ansi style
|
||||
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
|
||||
let mut last_match_end = 0;
|
||||
let style = Style::new().fg(White).on(Red);
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
||||
match cap {
|
||||
Ok(capture) => {
|
||||
let start = match capture.get(0) {
|
||||
Some(acap) => acap.start(),
|
||||
None => 0,
|
||||
};
|
||||
let end = match capture.get(0) {
|
||||
Some(acap) => acap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||
last_match_end = end;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error with regular expression capture".into(),
|
||||
e.to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
178
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
178
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpAliases;
|
||||
|
||||
impl Command for HelpAliases {
|
||||
fn name(&self) -> &str {
|
||||
"help aliases"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell aliases."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help aliases")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of alias to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in alias names and usage",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show all aliases",
|
||||
example: "help aliases",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single alias",
|
||||
example: "help aliases my-alias",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in alias names and usages",
|
||||
example: "help aliases --find my-alias",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_aliases(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_aliases(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||
let found_cmds_vec =
|
||||
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let Some(alias) = engine_state.find_decl(name.as_bytes(), &[]) else {
|
||||
return Err(ShellError::AliasNotFound(span(
|
||||
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||
)));
|
||||
};
|
||||
|
||||
let Some(alias) = engine_state.get_decl(alias).as_alias() else {
|
||||
return Err(ShellError::AliasNotFound(span(
|
||||
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||
)));
|
||||
};
|
||||
|
||||
let alias_expansion =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(&alias.wrapped_call.span));
|
||||
let usage = alias.usage();
|
||||
let extra_usage = alias.extra_usage();
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
long_desc.push_str(usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
|
||||
scope_data.collect_aliases(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpAliases;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpAliases {})
|
||||
}
|
||||
}
|
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||
Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpCommands;
|
||||
|
||||
impl Command for HelpCommands {
|
||||
fn name(&self) -> &str {
|
||||
"help commands"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell commands."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help commands")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of command to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in command names, usage, and search terms",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_commands(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_commands(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||
let found_cmds_vec = highlight_search_in_table(
|
||||
all_cmds_vec,
|
||||
&f.item,
|
||||
&["name", "usage", "search_terms"],
|
||||
&string_style,
|
||||
)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_commands(engine_state, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let output = engine_state
|
||||
.get_signatures_with_examples(false)
|
||||
.iter()
|
||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if !output.is_empty() {
|
||||
Ok(Value::String {
|
||||
val: output.join("======================\n\n"),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CommandNotFound(span(&[
|
||||
rest[0].span,
|
||||
rest[rest.len() - 1].span,
|
||||
])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||
let commands = engine_state.get_decls_sorted(false);
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
for (name_bytes, decl_id) in commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
let sig = decl.signature().update_from_command(name, decl.borrow());
|
||||
|
||||
let signatures = sig.to_string().trim_start().replace("\n ", "\n");
|
||||
let key = sig.name;
|
||||
let usage = sig.usage;
|
||||
let search_terms = sig.search_terms;
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String { val: key, span });
|
||||
|
||||
cols.push("category".into());
|
||||
vals.push(Value::string(sig.category.to_string(), span));
|
||||
|
||||
cols.push("command_type".into());
|
||||
vals.push(Value::String {
|
||||
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String { val: usage, span });
|
||||
|
||||
cols.push("signatures".into());
|
||||
vals.push(Value::String {
|
||||
val: if decl.is_parser_keyword() {
|
||||
"".to_string()
|
||||
} else {
|
||||
signatures
|
||||
},
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("search_terms".into());
|
||||
vals.push(Value::String {
|
||||
val: search_terms.join(", "),
|
||||
span,
|
||||
});
|
||||
|
||||
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
|
||||
found_cmds_vec
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpCommands;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpCommands {})
|
||||
}
|
||||
}
|
152
crates/nu-cmd-lang/src/core_commands/help_externs.rs
Normal file
152
crates/nu-cmd-lang/src/core_commands/help_externs.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{get_full_help, scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpExterns;
|
||||
|
||||
impl Command for HelpExterns {
|
||||
fn name(&self) -> &str {
|
||||
"help externs"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell externs."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help externs")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of extern to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in extern names and usage",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show all externs",
|
||||
example: "help externs",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single extern",
|
||||
example: "help externs smth",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in extern names and usages",
|
||||
example: "help externs --find smth",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_externs(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_externs(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||
let found_cmds_vec =
|
||||
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let output = engine_state
|
||||
.get_signatures_with_examples(false)
|
||||
.iter()
|
||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if !output.is_empty() {
|
||||
Ok(Value::String {
|
||||
val: output.join("======================\n\n"),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CommandNotFound(span(&[
|
||||
rest[0].span,
|
||||
rest[rest.len() - 1].span,
|
||||
])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope = ScopeData::new(engine_state, stack);
|
||||
scope.populate_all();
|
||||
scope.collect_externs(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpExterns;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpExterns {})
|
||||
}
|
||||
}
|
262
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
262
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
@ -0,0 +1,262 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, DeclId, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpModules;
|
||||
|
||||
impl Command for HelpModules {
|
||||
fn name(&self) -> &str {
|
||||
"help modules"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell modules."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"When requesting help for a single module, its commands and aliases will be highlighted if they
|
||||
are also available in the current scope. Commands/aliases that were imported under a different name
|
||||
(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help modules")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in module names and usage",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show all modules",
|
||||
example: "help modules",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single module",
|
||||
example: "help modules my-module",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in module names and usages",
|
||||
example: "help modules --find my-module",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_modules(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_modules(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||
let found_cmds_vec =
|
||||
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) else {
|
||||
return Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: name,
|
||||
span: span(&rest.iter().map(|r| r.span).collect::<Vec<Span>>()),
|
||||
});
|
||||
};
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let module_usage = engine_state.build_module_usage(module_id);
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const CB: &str = "\x1b[1;36m"; // cyan bold
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
if let Some((usage, extra_usage)) = module_usage {
|
||||
long_desc.push_str(&usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(&extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !module.decls.is_empty() || module.main.is_some() {
|
||||
let commands: Vec<(Vec<u8>, DeclId)> = engine_state
|
||||
.get_decls_sorted(false)
|
||||
.filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
|
||||
let mut module_commands: Vec<(Vec<u8>, DeclId)> = module
|
||||
.decls()
|
||||
.into_iter()
|
||||
.filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
module_commands.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let commands_str = module_commands
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
commands.iter().find(|(_, decl_id)| id == decl_id)
|
||||
{
|
||||
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let command_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{command_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if !module.decls.is_empty() {
|
||||
let aliases: Vec<(Vec<u8>, DeclId)> = engine_state
|
||||
.get_decls_sorted(false)
|
||||
.filter(|(_, id)| engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
|
||||
let mut module_aliases: Vec<(Vec<u8>, DeclId)> = module
|
||||
.decls()
|
||||
.into_iter()
|
||||
.filter(|(_, id)| engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
module_aliases.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let aliases_str = module_aliases
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
aliases.iter().find(|(_, alias_id)| id == alias_id)
|
||||
{
|
||||
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let alias_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{alias_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if module.env_block.is_some() {
|
||||
long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
|
||||
} else {
|
||||
long_desc.push_str(&format!(
|
||||
"This module {C}does not export{RESET} environment."
|
||||
));
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_modules();
|
||||
|
||||
scope_data.collect_modules(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpModules;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpModules {})
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value,
|
||||
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -17,7 +17,10 @@ impl Command for HelpOperators {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help operators").category(Category::Core)
|
||||
Signature::build("help operators")
|
||||
.category(Category::Core)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn run(
|
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Hide;
|
||||
|
||||
impl Command for Hide {
|
||||
fn name(&self) -> &str {
|
||||
"hide"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("hide")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module", SyntaxShape::String, "Module or module file")
|
||||
.optional(
|
||||
"members",
|
||||
SyntaxShape::Any,
|
||||
"Which members of the module to import",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide definitions in the current scope."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Definitions are hidden by priority: First aliases, then custom commands.
|
||||
|
||||
This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Hide the alias just defined",
|
||||
example: r#"alias lll = ls -l; hide lll"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Hide a custom command",
|
||||
example: r#"def say-hi [] { echo 'Hi!' }; hide say-hi"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -31,7 +31,7 @@ impl Command for HideEnv {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide environment variables in the current scope"
|
||||
"Hide environment variables in the current scope."
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -40,12 +40,12 @@ impl Command for HideEnv {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
let ignore_errors = call.has_flag("ignore-errors");
|
||||
|
||||
for name in env_var_names {
|
||||
if stack.remove_env_var(engine_state, &name.item).is_none() && !ignore_errors {
|
||||
if !stack.remove_env_var(engine_state, &name.item) && !ignore_errors {
|
||||
let all_names: Vec<String> = stack
|
||||
.get_env_var_names(engine_state)
|
||||
.iter()
|
||||
@ -58,7 +58,10 @@ impl Command for HideEnv {
|
||||
name.span,
|
||||
));
|
||||
} else {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span));
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime {
|
||||
envvar_name: name.item,
|
||||
span: name.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,7 +73,7 @@ impl Command for HideEnv {
|
||||
vec![Example {
|
||||
description: "Hide an environment variable",
|
||||
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
||||
result: Some(Value::boolean(false, Span::test_data())),
|
||||
result: Some(Value::test_bool(false)),
|
||||
}]
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ impl Command for If {
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("if")
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("cond", SyntaxShape::Expression, "condition to check")
|
||||
.required("cond", SyntaxShape::MathExpression, "condition to check")
|
||||
.required(
|
||||
"then_block",
|
||||
SyntaxShape::Block,
|
||||
@ -40,22 +40,13 @@ impl Command for If {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cond = call.positional_nth(0).expect("checked through parser");
|
||||
let then_block: Block = call.req(engine_state, stack, 1)?;
|
||||
let else_case = call.positional_nth(2);
|
||||
@ -111,12 +102,12 @@ impl Command for If {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
x => Err(ShellError::CantConvert(
|
||||
"bool".into(),
|
||||
x.get_type().to_string(),
|
||||
result.span()?,
|
||||
None,
|
||||
)),
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: "bool".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: result.span()?,
|
||||
help: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, Type, Value};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ignore;
|
||||
@ -11,7 +11,7 @@ impl Command for Ignore {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Ignore the output of the previous command in the pipeline"
|
||||
"Ignore the output of the previous command in the pipeline."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -30,7 +30,7 @@ impl Command for Ignore {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
input.into_value(call.head);
|
||||
Ok(PipelineData::empty())
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::eval_expression_with_input;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Let;
|
||||
@ -22,7 +22,7 @@ impl Command for Let {
|
||||
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
@ -47,7 +47,7 @@ impl Command for Let {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
@ -60,21 +60,23 @@ impl Command for Let {
|
||||
.as_keyword()
|
||||
.expect("internal error: missing keyword");
|
||||
|
||||
let rhs = eval_expression_with_input(
|
||||
let (rhs, external_failed) = eval_expression_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
keyword_expr,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?
|
||||
.0;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
)?;
|
||||
if external_failed {
|
||||
// rhs must be a PipelineData::ExternalStream and it's failed
|
||||
// return the failed stream (with a non-zero exit code) so the engine knows to stop running
|
||||
Ok(rhs)
|
||||
} else {
|
||||
stack.add_var(var_id, rhs.into_value(call.head));
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
@ -2,7 +2,7 @@ use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -19,26 +19,19 @@ impl Command for Loop {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("loop")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("block", SyntaxShape::Block, "block to loop")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let block: Block = call.req(engine_state, stack, 0)?;
|
||||
|
||||
loop {
|
||||
@ -65,9 +58,11 @@ impl Command for Loop {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(engine_state, stack, false, false)?;
|
||||
let exit_code = pipeline.drain_with_exit_code()?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
||||
exit_code,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,7 +74,7 @@ impl Command for Loop {
|
||||
vec![Example {
|
||||
description: "Loop while a condition is true",
|
||||
example: "mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x",
|
||||
result: Some(Value::int(11, Span::test_data())),
|
||||
result: Some(Value::test_int(11)),
|
||||
}]
|
||||
}
|
||||
}
|
124
crates/nu-cmd-lang/src/core_commands/match_.rs
Normal file
124
crates/nu-cmd-lang/src/core_commands/match_.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use nu_engine::{eval_block, eval_expression_with_input, CallExt};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Matcher, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Match;
|
||||
|
||||
impl Command for Match {
|
||||
fn name(&self) -> &str {
|
||||
"match"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Conditionally run a block on a matched value."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("match")
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("value", SyntaxShape::Any, "value to check")
|
||||
.required(
|
||||
"match_block",
|
||||
SyntaxShape::MatchBlock,
|
||||
"block to run if check succeeds",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let block = call.positional_nth(1);
|
||||
|
||||
if let Some(Expression {
|
||||
expr: Expr::MatchBlock(matches),
|
||||
..
|
||||
}) = block
|
||||
{
|
||||
for match_ in matches {
|
||||
let mut match_variables = vec![];
|
||||
if match_.0.match_value(&value, &mut match_variables) {
|
||||
// This case does match, go ahead and return the evaluated expression
|
||||
for match_variable in match_variables {
|
||||
stack.add_var(match_variable.0, match_variable.1);
|
||||
}
|
||||
|
||||
if let Some(block_id) = match_.1.as_block() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
return eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
);
|
||||
} else {
|
||||
return eval_expression_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
&match_.1,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|x| x.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::Empty)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Match on a value in range",
|
||||
example: "match 3 { 1..10 => 'yes!' }",
|
||||
result: Some(Value::test_string("yes!")),
|
||||
},
|
||||
Example {
|
||||
description: "Match on a field in a record",
|
||||
example: "match {a: 100} { {a: $my_value} => { $my_value } }",
|
||||
result: Some(Value::test_int(100)),
|
||||
},
|
||||
Example {
|
||||
description: "Match with a catch-all",
|
||||
example: "match 3 { 1 => { 'yes!' }, _ => { 'no!' } }",
|
||||
result: Some(Value::test_string("no!")),
|
||||
},
|
||||
Example {
|
||||
description: "Match against a list",
|
||||
example: "match [1, 2, 3] { [$a, $b, $c] => { $a + $b + $c }, _ => 0 }",
|
||||
result: Some(Value::test_int(6)),
|
||||
},
|
||||
Example {
|
||||
description: "Match against pipeline input",
|
||||
example: "{a: {b: 3}} | match $in {{a: { $b }} => ($b + 10) }",
|
||||
result: Some(Value::test_int(13)),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Match {})
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
mod alias;
|
||||
mod ast;
|
||||
mod break_;
|
||||
mod commandline;
|
||||
mod collect;
|
||||
mod const_;
|
||||
mod continue_;
|
||||
mod debug;
|
||||
mod def;
|
||||
mod def_env;
|
||||
mod describe;
|
||||
@ -19,6 +18,10 @@ mod export_use;
|
||||
mod extern_;
|
||||
mod for_;
|
||||
pub mod help;
|
||||
pub mod help_aliases;
|
||||
pub mod help_commands;
|
||||
pub mod help_externs;
|
||||
pub mod help_modules;
|
||||
mod help_operators;
|
||||
mod hide;
|
||||
mod hide_env;
|
||||
@ -26,7 +29,7 @@ mod if_;
|
||||
mod ignore;
|
||||
mod let_;
|
||||
mod loop_;
|
||||
mod metadata;
|
||||
mod match_;
|
||||
mod module;
|
||||
mod mut_;
|
||||
pub(crate) mod overlay;
|
||||
@ -37,11 +40,10 @@ mod version;
|
||||
mod while_;
|
||||
|
||||
pub use alias::Alias;
|
||||
pub use ast::Ast;
|
||||
pub use break_::Break;
|
||||
pub use commandline::Commandline;
|
||||
pub use collect::Collect;
|
||||
pub use const_::Const;
|
||||
pub use continue_::Continue;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
pub use def_env::DefEnv;
|
||||
pub use describe::Describe;
|
||||
@ -57,6 +59,10 @@ pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
pub use for_::For;
|
||||
pub use help::Help;
|
||||
pub use help_aliases::HelpAliases;
|
||||
pub use help_commands::HelpCommands;
|
||||
pub use help_externs::HelpExterns;
|
||||
pub use help_modules::HelpModules;
|
||||
pub use help_operators::HelpOperators;
|
||||
pub use hide::Hide;
|
||||
pub use hide_env::HideEnv;
|
||||
@ -64,7 +70,7 @@ pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use let_::Let;
|
||||
pub use loop_::Loop;
|
||||
pub use metadata::Metadata;
|
||||
pub use match_::Match;
|
||||
pub use module::Module;
|
||||
pub use mut_::Mut;
|
||||
pub use overlay::*;
|
||||
@ -73,8 +79,8 @@ pub use try_::Try;
|
||||
pub use use_::Use;
|
||||
pub use version::Version;
|
||||
pub use while_::While;
|
||||
#[cfg(feature = "plugin")]
|
||||
//#[cfg(feature = "plugin")]
|
||||
mod register;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
//#[cfg(feature = "plugin")]
|
||||
pub use register::Register;
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Module;
|
||||
@ -11,7 +13,7 @@ impl Command for Module {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom module"
|
||||
"Define a custom module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -37,7 +39,7 @@ impl Command for Module {
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
@ -46,17 +48,17 @@ impl Command for Module {
|
||||
Example {
|
||||
description: "Define a custom command in a module and call it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||
result: Some(Value::string("foo", Span::test_data())),
|
||||
result: Some(Value::test_string("foo")),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module",
|
||||
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||
result: Some(Value::string("BAZ", Span::test_data())),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
},
|
||||
Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
result: Some(Value::string("BAZ", Span::test_data())),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
},
|
||||
]
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::eval_expression_with_input;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Mut;
|
||||
@ -22,7 +22,7 @@ impl Command for Mut {
|
||||
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
@ -47,7 +47,7 @@ impl Command for Mut {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user