forked from extern/nushell
Compare commits
622 Commits
Author | SHA1 | Date | |
---|---|---|---|
6cc4ef6c70 | |||
517173bb8c | |||
e415be6c0e | |||
59332562bb | |||
3d8d7787de | |||
611fe41788 | |||
a6118eed8d | |||
d4798d6ee1 | |||
5ea245badf | |||
5ee7847035 | |||
8a812cf03c | |||
9a1cedfd08 | |||
766d1ef374 | |||
beec658872 | |||
b90d701f89 | |||
be5d71ea47 | |||
f1bde69131 | |||
36ae384fb3 | |||
2c4048eb43 | |||
b9195c2668 | |||
bb968304da | |||
ca9bf19041 | |||
ecfee4c542 | |||
acb34561eb | |||
43aec8cdbe | |||
e46d610f77 | |||
1d95861a09 | |||
f48de73236 | |||
0b4daa66b0 | |||
412952182f | |||
014d36b17a | |||
f44f3a8af1 | |||
457514590d | |||
843d8c2242 | |||
ce4ae00a6f | |||
4f7f6a2932 | |||
7039602e4d | |||
acb7aff6fb | |||
8940ee6c3f | |||
3b26b4355e | |||
b56e603c58 | |||
66c2a36123 | |||
8838815737 | |||
834522d002 | |||
f281cd5aa3 | |||
5add5cbd12 | |||
902aad6016 | |||
13f87857cf | |||
e0cc2c9112 | |||
92ab8b831b | |||
6a7a60429f | |||
79fd7d54b2 | |||
ebca840d91 | |||
17b2bcc125 | |||
24a98f8999 | |||
e49b359848 | |||
c9fb381d69 | |||
8224ec49bc | |||
fe7e87ee02 | |||
a3dce8ff19 | |||
89f3cbf318 | |||
3f555a6836 | |||
4fdf5c663c | |||
1ec41a0ab4 | |||
ab0a6b6ca6 | |||
e3bf6fdfc0 | |||
46c0d29c08 | |||
76ccd5668a | |||
c6436eb32f | |||
b2c29117d9 | |||
ffb1dfb012 | |||
88c6fa9933 | |||
60df45a390 | |||
10aa86272b | |||
d37e6ba3b5 | |||
5eee33c7e4 | |||
5e748ae8fc | |||
50e53e788a | |||
7336e1df1a | |||
a724a8fe7d | |||
c731a4e275 | |||
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba | |||
e76b3d61de | |||
1adebefc3e | |||
d1e1d0ac3e | |||
b398448cd9 | |||
02f92fa527 | |||
773d167449 | |||
aa92141ad7 | |||
80624267fd | |||
2030e25ddc | |||
247fff424d | |||
c902d8bc0c | |||
b0e5723a68 | |||
9273bb3f72 | |||
f7d3ccfc70 | |||
d86350af80 | |||
14512988ba | |||
33e1120add | |||
3278d290be | |||
daec3fc3d3 | |||
a6ba58ec41 | |||
65327e0e7e | |||
3ed3712fdc | |||
f46962d236 | |||
aa4778ff07 | |||
e81689f2c0 | |||
4656310a1c | |||
34e58bc5d6 | |||
fbe9d6f529 | |||
e266590813 | |||
b27148d14b | |||
4858a9a817 | |||
3ec53e544c | |||
c52d45cb97 | |||
11531b7630 | |||
a098a27837 | |||
2591bd8c63 | |||
a03fb946d9 | |||
9c58f2a522 | |||
3cb9147f22 | |||
f1d72e2670 | |||
f1e7a01b2e | |||
b88ace4cde | |||
34d7c17e78 | |||
3f1824111d | |||
fbae137442 | |||
9850424251 | |||
918ec9daa8 | |||
7b502a4c7f | |||
7b07e976b8 | |||
e45b169cba | |||
5ebfa10495 | |||
3f93dc2f1d | |||
a43514deb2 | |||
ab77bf3289 | |||
0afe1e4e67 | |||
ef26d539a7 | |||
fce8581321 | |||
ba6abd77c9 | |||
a7295c8f1b | |||
2a310ef187 | |||
530e250573 | |||
6fbc76bc0f | |||
884382bac4 | |||
d97975e9fa | |||
839b264261 | |||
7ef4e5f940 | |||
646aace05b | |||
772ad896c8 | |||
9c4bbe3c63 | |||
c5ca839294 | |||
5337a6dffa | |||
56ce10347e | |||
37bc90c62a | |||
ad7522bba0 | |||
bbcf374886 | |||
99c42582fe | |||
5a56d47f25 | |||
529c98085a | |||
2b955f82b7 | |||
1843fdc060 | |||
ec4e3a6d5c | |||
4ab468e65f | |||
1d18f6947e | |||
df3b6d9d26 | |||
4bbdb73668 | |||
62d3497bbb | |||
e614970c08 | |||
d931331b57 | |||
f18da2609a | |||
2ef9cc118e | |||
33674d3a98 | |||
0167649e6f | |||
cc263ee15d | |||
a4809f2e68 | |||
21770367e2 | |||
6145f734b7 | |||
9d8d305e9d | |||
eb55fd2383 | |||
613d2fb8df | |||
8783742060 | |||
20528e96c7 | |||
3b6c4c1bb5 | |||
cb18dd5200 | |||
ccebdd7a7f | |||
c3efb12733 | |||
d885258dc7 | |||
2da915d0c7 | |||
ae64c58f59 | |||
ff6868b329 | |||
47ef193600 | |||
c2f4969d4f | |||
08c98967e0 | |||
8d091f6f83 | |||
58094987ff | |||
ce26ef97e4 | |||
8f9bd4a299 | |||
45dd7d8770 | |||
0f10d984c3 | |||
2e5d981a09 | |||
c74254c2cb | |||
271fda7c91 | |||
0e5886ace1 | |||
4b89c5f900 | |||
dcab255d59 | |||
fc8512be39 | |||
e10ef4aaae | |||
0b70ca8451 | |||
555d9ee763 | |||
121b801baa | |||
9adcecbbf1 | |||
9f131d998d | |||
cd0a04f02a | |||
aaf5684f9c | |||
2f0cb044a5 | |||
8b55757a0b | |||
84fae6e07e | |||
63e220a763 | |||
a96fc21f88 | |||
1ba5b25b29 | |||
a871f2344a | |||
a217bc0715 | |||
34ab4d8360 | |||
48f1c3a49e | |||
692376e830 | |||
cc99df5ef1 | |||
d255a2a050 | |||
c07835f3ad | |||
78a5067434 | |||
cdeb8de75d | |||
606547ecb4 | |||
3b809b38e8 | |||
7c49a42b68 | |||
87823b0cb5 | |||
ebf845f431 | |||
ce6df93d05 | |||
e7958bebac | |||
233afebdf0 | |||
56069af42d | |||
376d22e331 | |||
7fc8ff60fd | |||
1f4791a191 | |||
2ac7a4d48d | |||
01386f4d58 | |||
1086fbe9b5 | |||
a83bd4ab20 | |||
26caf7e1b2 | |||
dd2a0e35f4 | |||
6a4eabf5c7 | |||
0e2c888f73 | |||
c140da5740 | |||
586c0ea3d8 | |||
d6f4189c7b | |||
7a820b1304 | |||
767201c40d | |||
3c3614a120 | |||
9e24e452a5 | |||
2cffff0c1b | |||
cf2e9cf481 | |||
6b2c7a4c86 | |||
98e199f7b5 | |||
10e463180e | |||
c9d0003818 | |||
e2a21afca8 | |||
2ea209bcc0 | |||
e049ca8ebf | |||
9037a9467b | |||
4c6cf36aa5 | |||
c92211c016 | |||
8bd6b5b913 | |||
b67fe31544 | |||
c8adb06ca7 | |||
9695331eed | |||
d42cfab6ef | |||
2b7c811402 | |||
c6cb491e77 | |||
e2a4632159 | |||
65f0edd14b | |||
b2c466bca6 | |||
6b4e577032 | |||
b12a3dd0e5 | |||
d856ac92f4 | |||
f5856b0914 | |||
8c675a0d31 | |||
86a0e77065 | |||
72c27bd095 | |||
e4e27b6e11 | |||
475d32045f | |||
3643ee6dfd | |||
32e4535f24 | |||
daa2148136 | |||
9097e865ca | |||
894d3e7452 | |||
5a5c65ee4b | |||
8b35239bce | |||
87e2fa137a | |||
46f64c6fdc | |||
10536f70f3 | |||
0812a08bfb | |||
5706eddee3 | |||
0b429fde24 | |||
388ff78a26 | |||
7d46177cf3 | |||
8a0bd20e84 | |||
a1a5a3646b | |||
453c11b4b5 | |||
c66b97126f | |||
0646f1118c | |||
0bcfa12e0d | |||
b2ec32fdf0 | |||
8f00848ff9 | |||
604025fe34 | |||
98126e2981 | |||
db9b88089e | |||
a35a71fd82 | |||
558cd58d09 | |||
410f3ef0f0 | |||
ae765c71fd | |||
e5684bc34c | |||
b4a7e7e6e9 | |||
41669e60c8 | |||
eeaca50dee | |||
d8d88cd395 | |||
9aabafeb41 | |||
9ced5915ff | |||
9d0be7d96f | |||
57a6465ba0 | |||
5cc6505512 | |||
3d45f77692 | |||
e01974b7ab | |||
1f01677b7b | |||
58ee2bf06a | |||
7bf09559a6 | |||
8dea08929a | |||
26f31da711 | |||
d95a065e3d | |||
ed50210832 | |||
ceafe434b5 | |||
89b374cb16 | |||
47c1f475bf | |||
61e027b227 | |||
58ab5aa887 | |||
2b2117173c | |||
f2a79cf381 | |||
ad9449bf00 | |||
c2f8f4bd9b | |||
8b6232ac87 | |||
93a965e3e2 | |||
217c2bae99 | |||
b9bbf0c10f | |||
a54f9719e5 | |||
a5470b2362 | |||
c1bf9fd897 | |||
f3036b8cfd | |||
9b6b817276 | |||
9e3c64aa84 | |||
920e0acb85 | |||
b7d3623e53 | |||
3676a8a48d | |||
f85a1d003c | |||
121e8678b6 | |||
e4c512e33d | |||
81df42d63b | |||
6802a4ee21 | |||
c0ce78f892 | |||
221f36ca65 | |||
125e60d06a | |||
83458510a9 | |||
eac5f62959 | |||
b19cc799aa | |||
efa56d0147 | |||
47f6d20131 | |||
e0b4ab09eb | |||
8abf28093a | |||
d1687df067 | |||
e77219a59f | |||
22edb37162 | |||
1ac87715ff | |||
de162c9aea | |||
390d06d4e7 | |||
89acbda877 | |||
0d40d0438f | |||
1e8212a938 | |||
2da8310b11 | |||
c16d8f0d5f | |||
2ac5b0480a | |||
4e90b478b7 | |||
3a38fb94f0 | |||
c6f6dcb57c | |||
b80299eba7 | |||
a48616697a | |||
b82dccf0bd | |||
84caf8859f | |||
be7f35246e | |||
3917fda7ed | |||
3b357e5402 | |||
79da470239 | |||
37949e70e0 | |||
5d00ecef56 | |||
6dde231dde | |||
58fa2e51a2 | |||
cf0877bf72 | |||
a0db4ce747 | |||
6ee13126f7 | |||
1c15a4ed3a | |||
7aabc381a3 | |||
8c9dced71b | |||
06d5a31301 | |||
ffbc0b0180 | |||
c0901ef707 | |||
d3e84daa49 | |||
228ede18cf | |||
c5a69271a2 | |||
dc9d939c83 | |||
32f0f94b46 | |||
a142d1a192 | |||
173d60d59d | |||
f2989bf704 | |||
575ddbd4ef | |||
ef9b72d360 | |||
25349a1eac | |||
99e4c44862 | |||
1345f97202 | |||
f02076daa8 | |||
533e04a60a | |||
13c152b00f | |||
f231a6df4a | |||
3c0bccb900 | |||
f43a65d7a7 | |||
0827ed143d | |||
4b84825dbf | |||
82ae06865c | |||
128ce6f9b7 | |||
44cbd88b55 | |||
7164929c61 | |||
848ff8453b | |||
f94ca6cfde | |||
fab3f8fd40 | |||
dbcfcdae89 | |||
08aa248c42 | |||
9f07bcc66f | |||
2caa44cea8 | |||
28c21121cf | |||
a17d46f200 | |||
6cc8402127 | |||
5f0ad1d6ad | |||
8d7bb9147e | |||
bc48b4553c | |||
28c07a5072 | |||
30c8dabeb4 | |||
8b368b6a4e | |||
8c0d60d0fb | |||
8b0a4ccf4c | |||
cfe4eff566 | |||
38f3957edf | |||
cb66d2bcad | |||
ff73623873 | |||
d1c719a8cc | |||
4d854f36af | |||
8d5848c955 | |||
fe88d58b1e | |||
42dbfd1fa0 | |||
534e1fc3ce | |||
ff946a2f21 | |||
3c0cbec993 | |||
48e29e9ed6 | |||
ff53352afe | |||
4fd4136d50 | |||
dc1248a454 | |||
de554f8e5f | |||
44979f3051 | |||
7ae7394c85 | |||
9dbf7556b8 | |||
caafd26deb | |||
43a218240c | |||
11d7d8ea1e | |||
2dea9e6f1f | |||
c5cb369d8d | |||
b6959197bf | |||
d5b99ae316 | |||
9d10007085 | |||
2e0b964d5b | |||
5bae7e56ef | |||
b42ef45c7c | |||
3423cd54a1 | |||
837f0463eb | |||
56f6f683fc | |||
c57f41e5f2 | |||
8c74b1e437 | |||
8318d59ef1 | |||
64efa30f3e | |||
820a6bfb08 | |||
b8d253cbd7 | |||
3c421c5726 | |||
75b2d26187 | |||
17a5aa3052 | |||
e4a22799d5 | |||
fda456e469 | |||
e5d38dcff6 | |||
a82fa75c31 | |||
0c16464320 | |||
888758b813 | |||
cb909f810e | |||
a75318d7e8 | |||
7a9bf06005 | |||
a06299c77a | |||
e4bcd1934d | |||
4673adecc5 | |||
1b8051ece5 | |||
d44059c36b | |||
b79abdb2a5 | |||
ee8a0c9477 | |||
41853b9f18 | |||
997d56a288 | |||
0769e9b750 | |||
f5519e2a09 | |||
8259d463aa | |||
e2c015f725 | |||
eb12fffbc6 | |||
c42096c34e | |||
46eb34b35d | |||
23a73cd31f |
@ -1,6 +1,7 @@
|
|||||||
# increase the default windows stack size
|
|
||||||
[target.x86_64-pc-windows-msvc]
|
[target.x86_64-pc-windows-msvc]
|
||||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
# increase the default windows stack size
|
||||||
|
# statically link the CRT so users don't have to install it
|
||||||
|
rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
# keeping this but commentting out in case we need them in the future
|
# keeping this but commentting out in case we need them in the future
|
||||||
|
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
||||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: context
|
id: context
|
||||||
attributes:
|
attributes:
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: "When you want a new feature for something that doesn't already exist"
|
description: "When you want a new feature for something that doesn't already exist"
|
||||||
|
labels: "enhancement"
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
|
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Question
|
||||||
|
description: "When you have a question to ask"
|
||||||
|
labels: "question"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Question
|
||||||
|
description: Leave your question here
|
||||||
|
placeholder: |
|
||||||
|
A clear and concise question
|
||||||
|
Example: Is there any equivalent of bash's $CDPATH in Nu?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context and details
|
||||||
|
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
|
||||||
|
validations:
|
||||||
|
required: false
|
25
.github/pull_request_template.md
vendored
25
.github/pull_request_template.md
vendored
@ -1,11 +1,26 @@
|
|||||||
# Description
|
# Description
|
||||||
|
|
||||||
(description of your pull request here)
|
(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)
|
||||||
|
|
||||||
# Tests
|
# Major Changes
|
||||||
|
|
||||||
|
If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose).
|
||||||
|
Making sure we're all on board with the change saves everybody's time.
|
||||||
|
Thanks!
|
||||||
|
|
||||||
|
# Tests + Formatting
|
||||||
|
|
||||||
|
Make sure you've done the following, if applicable:
|
||||||
|
|
||||||
|
- Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder)
|
||||||
|
- Try to think about corner cases and various ways how your changes could break. Cover those in the tests
|
||||||
|
|
||||||
Make sure you've run and fixed any issues with these commands:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
- `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
- `cargo test --workspace --features=extra` to check that all tests pass
|
||||||
|
|
||||||
|
# After Submitting
|
||||||
|
|
||||||
|
* Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||||
|
72
.github/workflows/ci.yml
vendored
72
.github/workflows/ci.yml
vendored
@ -20,28 +20,19 @@ jobs:
|
|||||||
NUSHELL_CARGO_TARGET: ci
|
NUSHELL_CARGO_TARGET: ci
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: "v2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
@ -71,21 +62,13 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||||
@ -106,37 +89,26 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: "2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: install
|
command: install
|
||||||
args: --path=. --profile ci --no-default-features
|
args: --path=. --profile ci --no-default-features
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
- run: python -m pip install tox
|
- run: python -m pip install tox
|
||||||
|
|
||||||
- name: Install virtualenv
|
- name: Install virtualenv
|
||||||
run: |
|
run: git clone https://github.com/pypa/virtualenv.git
|
||||||
git clone https://github.com/kubouch/virtualenv.git && \
|
|
||||||
cd virtualenv && \
|
|
||||||
git checkout engine-q-update
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
@ -159,23 +131,19 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --profile ci --package nu_plugin_*
|
args: --profile ci --package nu_plugin_*
|
||||||
|
41
.github/workflows/manual.yml
vendored
Normal file
41
.github/workflows/manual.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# This is a basic workflow that is manually triggered
|
||||||
|
# Don't run it unless you know what you are doing
|
||||||
|
|
||||||
|
name: Manual Workflow for Winget Submission
|
||||||
|
|
||||||
|
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||||
|
# or API.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# Inputs the workflow accepts.
|
||||||
|
inputs:
|
||||||
|
ver:
|
||||||
|
# Friendly description to be shown in the UI instead of 'ver'
|
||||||
|
description: 'The nushell version to release'
|
||||||
|
# Default value if no value is explicitly provided
|
||||||
|
default: '0.66.0'
|
||||||
|
# Input has to be provided for the workflow to run
|
||||||
|
required: true
|
||||||
|
uri:
|
||||||
|
# Friendly description to be shown in the UI instead of 'uri'
|
||||||
|
description: 'The nushell windows .msi package URI to publish'
|
||||||
|
# Default value if no value is explicitly provided
|
||||||
|
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
|
||||||
|
# Input has to be provided for the workflow to run
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job
|
||||||
|
rls-winget-pkg:
|
||||||
|
name: Publish winget package manually
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Runs commands using the runners shell
|
||||||
|
- name: Submit package to Windows Package Manager Community Repository Manually
|
||||||
|
run: |
|
||||||
|
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||||
|
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}
|
34
.github/workflows/release-pkg.nu
vendored
34
.github/workflows/release-pkg.nu
vendored
@ -41,7 +41,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
} else {
|
} else {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
sudo apt install musl-tools -y
|
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
# Build for Windows without static-link-openssl feature
|
# Build for Windows without static-link-openssl feature
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in ['windows-latest'] {
|
if $os in ['windows-latest'] {
|
||||||
if ($flags | str trim | empty?) {
|
if ($flags | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra
|
cargo build --release --all --target $target --features=extra
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra $flags
|
cargo build --release --all --target $target --features=extra $flags
|
||||||
@ -76,11 +76,11 @@ cp -v README.release.txt $'($dist)/README.txt'
|
|||||||
|
|
||||||
$'(char nl)Check binary release version detail:'; hr-line
|
$'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os == 'windows-latest' {
|
let ver = if $os == 'windows-latest' {
|
||||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||||
} else {
|
} else {
|
||||||
(do -i { ./output/nu -c 'version' }) | str collect
|
(do -i { ./output/nu -c 'version' }) | str join
|
||||||
}
|
}
|
||||||
if ($ver | str trim | empty?) {
|
if ($ver | str trim | is-empty) {
|
||||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||||
} else { $ver }
|
} else { $ver }
|
||||||
|
|
||||||
@ -90,10 +90,16 @@ if ($ver | str trim | empty?) {
|
|||||||
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||||
|
|
||||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
let files = (ls | get name)
|
||||||
|
let dest = $'($bin)-($version)-($target)'
|
||||||
|
let archive = $'($dist)/($dest).tar.gz'
|
||||||
|
|
||||||
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
|
mkdir $dest
|
||||||
tar czf $archive *
|
$files | each {|it| mv $it $dest } | ignore
|
||||||
|
|
||||||
|
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||||
|
|
||||||
|
tar -czf $archive $dest
|
||||||
print $'archive: ---> ($archive)'; ls $archive
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
echo $'::set-output name=archive::($archive)'
|
echo $'::set-output name=archive::($archive)'
|
||||||
|
|
||||||
@ -102,8 +108,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
|
||||||
$'(char nl)Download less related stuffs...'; hr-line
|
$'(char nl)Download less related stuffs...'; hr-line
|
||||||
curl https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o $'($dist)\less.exe'
|
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
||||||
curl https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o $'($dist)\LICENSE-for-less.txt'
|
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||||
|
|
||||||
# Create Windows msi release package
|
# Create Windows msi release package
|
||||||
if (get-env _EXTRA_) == 'msi' {
|
if (get-env _EXTRA_) == 'msi' {
|
||||||
@ -113,7 +119,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
cd $src; hr-line
|
cd $src; hr-line
|
||||||
# Wix need the binaries be stored in target/release/
|
# Wix need the binaries be stored in target/release/
|
||||||
cp -r $'($dist)/*' target/release/
|
cp -r $'($dist)/*' target/release/
|
||||||
cargo install cargo-wix --version 0.3.2
|
cargo install cargo-wix --version 0.3.3
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
echo $'::set-output name=archive::($wixRelease)'
|
echo $'::set-output name=archive::($wixRelease)'
|
||||||
|
|
||||||
@ -124,14 +130,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
7z a $archive *
|
7z a $archive *
|
||||||
print $'archive: ---> ($archive)';
|
print $'archive: ---> ($archive)';
|
||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | empty?) {
|
if not ($pkg | is-empty) {
|
||||||
echo $'::set-output name=archive::($pkg | get 0)'
|
echo $'::set-output name=archive::($pkg | get 0)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def 'cargo-build-nu' [ options: string ] {
|
def 'cargo-build-nu' [ options: string ] {
|
||||||
if ($options | str trim | empty?) {
|
if ($options | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||||
@ -143,7 +149,7 @@ def 'hr-line' [
|
|||||||
--blank-line(-b): bool
|
--blank-line(-b): bool
|
||||||
] {
|
] {
|
||||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||||
if $blank-line { char nl }
|
if $blank_line { char nl }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the specified env key's value or ''
|
# Get the specified env key's value or ''
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -70,9 +70,9 @@ jobs:
|
|||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v1
|
uses: hustcer/setup-nu@v2.1
|
||||||
with:
|
with:
|
||||||
version: 0.63.0
|
version: 0.69.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
6
.github/workflows/winget-submission.yml
vendored
6
.github/workflows/winget-submission.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
run: |
|
run: |
|
||||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
|
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -22,6 +22,10 @@ debian/nu/
|
|||||||
# VSCode's IDE items
|
# VSCode's IDE items
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
# Visual Studio Extension SourceGear Rust items
|
||||||
|
VSWorkspaceSettings.json
|
||||||
|
unstable_cargo_features.txt
|
||||||
|
|
||||||
# Helix configuration folder
|
# Helix configuration folder
|
||||||
.helix/*
|
.helix/*
|
||||||
.helix
|
.helix
|
||||||
@ -29,3 +33,9 @@ debian/nu/
|
|||||||
# Coverage tools
|
# Coverage tools
|
||||||
lcov.info
|
lcov.info
|
||||||
tarpaulin-report.html
|
tarpaulin-report.html
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/*
|
||||||
|
*.rsproj
|
||||||
|
*.rsproj.user
|
||||||
|
*.sln
|
@ -1,21 +1,29 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Welcome to nushell!
|
Welcome to Nushell and thank you for considering contributing!
|
||||||
|
|
||||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
## Review Process
|
||||||
|
|
||||||
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
|
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||||
|
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
||||||
|
So, please, reach out and tell us what you want to do.
|
||||||
|
This will significantly increase the chance of your PR being accepted.
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
The review process can be summarized as follows:
|
||||||
|
1. You want to make some change to Nushell that is more involved than simple bug-fixing.
|
||||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
2. Go to [Discord](https://discordapp.com/invite/NtAbbGn) or a [GitHub issue](https://github.com/nushell/nushell/issues/new/choose) and chat with some core team members and/or other contributors about it.
|
||||||
<!--WIP-->
|
3. After getting a green light from the core team, implement the feature, open a pull request (PR) and write a concise but comprehensive description of the change.
|
||||||
|
4. If your PR includes any use-facing features (such as adding a flag to a command), clearly list them in the PR description.
|
||||||
|
5. Then, core team members and other regular contributors will review the PR and suggest changes.
|
||||||
|
6. When we all agree, the PR will be merged.
|
||||||
|
7. If your PR includes any user-facing features, make sure the changes are also reflected in [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||||
|
8. Congratulate yourself, you just improved Nushell! :-)
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
### Set up
|
### Setup
|
||||||
|
|
||||||
This is no different than other Rust projects.
|
Nushell requires a recent Rust toolchain and some dependencies; [refer to the Nu Book for up-to-date requirements](https://www.nushell.sh/book/installation.html#build-from-source). After installing dependencies, you should be able to clone+build Nu like any other Rust project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/nushell/nushell
|
git clone https://github.com/nushell/nushell
|
||||||
@ -28,24 +36,24 @@ cargo build
|
|||||||
- Build and run Nushell:
|
- Build and run Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release && cargo run --release
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
- Build and run with extra features:
|
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --features=extra && cargo run --release --features=extra
|
cargo run --features=extra
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run Clippy on Nushell:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo clippy --all --features=stable
|
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests:
|
- Run all tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo test --all --features=stable
|
cargo test --workspace --features=extra
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests for a specific command
|
- Run all tests for a specific command
|
||||||
@ -71,5 +79,11 @@ cargo build
|
|||||||
- To view verbose logs when developing, enable the `trace` log level.
|
- To view verbose logs when developing, enable the `trace` log level.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --features=extra && cargo run --release --features=extra -- --log-level trace
|
cargo run --release --features=extra -- --log-level trace
|
||||||
|
```
|
||||||
|
|
||||||
|
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
||||||
|
```shell
|
||||||
|
cargo run --release --features=extra -- --log-level trace --log-target file
|
||||||
|
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||||
```
|
```
|
||||||
|
2066
Cargo.lock
generated
2066
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
77
Cargo.toml
77
Cargo.toml
@ -8,10 +8,9 @@ exclude = ["images"]
|
|||||||
homepage = "https://www.nushell.sh"
|
homepage = "https://www.nushell.sh"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.63.1"
|
version = "0.71.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -28,57 +27,66 @@ members = [
|
|||||||
"crates/nu_plugin_gstat",
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu_plugin_query",
|
||||||
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.19"
|
chrono = { version = "0.4.21", features = ["serde"] }
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
ctrlc = "3.2.1"
|
ctrlc = "3.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "4.5.0"
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.63.1" }
|
nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.63.1" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.63.1" }
|
nu-command = { path="./crates/nu-command", version = "0.71.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.63.1" }
|
nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.63.1" }
|
nu-json = { path="./crates/nu-json", version = "0.71.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.63.1" }
|
nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.63.1" }
|
nu-path = { path="./crates/nu-path", version = "0.71.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.63.1" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.63.1" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.63.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.63.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.71.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.63.1" }
|
nu-table = { path = "./crates/nu-table", version = "0.71.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.63.1" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.63.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
|
||||||
pretty_env_logger = "0.4.0"
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
|
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
|
simplelog = "0.12.0"
|
||||||
|
time = "0.3.12"
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||||
# Our dependencies don't use OpenSSL on Windows
|
# Our dependencies don't use OpenSSL on Windows
|
||||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||||
|
signal-hook = { version = "0.3.14", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
nu-test-support = { path="./crates/nu-test-support", version = "0.63.1" }
|
|
||||||
tempfile = "3.2.0"
|
|
||||||
assert_cmd = "2.0.2"
|
|
||||||
pretty_assertions = "1.0.0"
|
|
||||||
serial_test = "0.5.1"
|
|
||||||
hamcrest2 = "0.3.0"
|
|
||||||
rstest = "0.12.0"
|
|
||||||
itertools = "0.10.3"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
winres = "0.1"
|
winres = "0.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
|
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
|
||||||
|
atty = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
|
||||||
|
tempfile = "3.2.0"
|
||||||
|
assert_cmd = "2.0.2"
|
||||||
|
pretty_assertions = "1.0.0"
|
||||||
|
serial_test = "0.8.0"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||||
|
extra = ["default", "dataframe", "database"]
|
||||||
default = ["plugin", "which-support", "trash-support"]
|
default = ["plugin", "which-support", "trash-support"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
extra = ["default", "dataframe", "database"]
|
|
||||||
wasi = []
|
wasi = []
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
@ -118,3 +126,8 @@ debug = false
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# To use a development version of a dependency please use a global override here
|
||||||
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
|
[patch.crates-io]
|
||||||
|
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||||
|
249
README.md
249
README.md
@ -1,5 +1,4 @@
|
|||||||
# README
|
# Nushell <!-- omit in toc -->
|
||||||
|
|
||||||
[](https://crates.io/crates/nu)
|
[](https://crates.io/crates/nu)
|
||||||

|

|
||||||
[](https://discord.gg/NtAbbGn)
|
[](https://discord.gg/NtAbbGn)
|
||||||
@ -8,128 +7,100 @@
|
|||||||

|

|
||||||

|

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

|

|
||||||
|
|
||||||
|
## Table of Contents <!-- omit in toc -->
|
||||||
|
|
||||||
|
- [Status](#status)
|
||||||
|
- [Learning About Nu](#learning-about-nu)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Philosophy](#philosophy)
|
||||||
|
- [Pipelines](#pipelines)
|
||||||
|
- [Opening files](#opening-files)
|
||||||
|
- [Plugins](#plugins)
|
||||||
|
- [Goals](#goals)
|
||||||
|
- [Progress](#progress)
|
||||||
|
- [Officially Supported By](#officially-supported-by)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This project has reached a minimum-viable product level of quality.
|
This project has reached a minimum-viable-product level of quality. Many people use it as their daily driver, but it may be unstable for some commands. Nu's design is subject to change as it matures.
|
||||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
|
||||||
Future releases will work to fill out missing features and improve stability.
|
|
||||||
Its design is also subject to change as it matures.
|
|
||||||
|
|
||||||
Nu comes with a set of built-in commands (listed below).
|
## Learning About Nu
|
||||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
|
||||||
|
|
||||||
## Learning more
|
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
There are a few good resources to learn about Nu.
|
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||||
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
|
||||||
The book focuses on using Nu and its core concepts.
|
|
||||||
|
|
||||||
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://www.nushell.sh/contributor-book/) to help you get started.
|
|
||||||
There are also [good first issues](https://github.com/nushell/nushell/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to help you dive in.
|
|
||||||
|
|
||||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
|
||||||
|
|
||||||
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Local
|
To quickly install Nu:
|
||||||
|
|
||||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
```bash
|
||||||
|
# Linux and macOS
|
||||||
To build Nu, you will need to use the **latest stable (1.60 or later)** version of the compiler.
|
brew install nushell
|
||||||
|
# Windows
|
||||||
Required dependencies:
|
|
||||||
|
|
||||||
- pkg-config and libssl (only needed on Linux)
|
|
||||||
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
|
||||||
|
|
||||||
Optional dependencies:
|
|
||||||
|
|
||||||
- To use Nu with all possible optional features enabled, you'll also need the following:
|
|
||||||
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
|
||||||
|
|
||||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
|
||||||
|
|
||||||
For Windows users, you may also need to install the [Microsoft Visual C++ 2015 Redistributables](https://docs.microsoft.com/cpp/windows/latest-supported-vc-redist).
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo install nu
|
|
||||||
```
|
|
||||||
|
|
||||||
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
winget install nushell
|
winget install nushell
|
||||||
```
|
```
|
||||||
|
|
||||||
To install Nu via the [Chocolatey](https://chocolatey.org) package manager:
|
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||||
|
|
||||||
```shell
|
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||||
choco install nushell
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo build --workspace --features=extra
|
|
||||||
```
|
|
||||||
### Packaging status
|
|
||||||
|
|
||||||
[](https://repology.org/project/nushell/versions)
|
[](https://repology.org/project/nushell/versions)
|
||||||
|
|
||||||
#### Fedora
|
|
||||||
|
|
||||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
Rather than thinking of files and data as raw streams of text, Nu looks at each input as something with structure.
|
||||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
For example, when you list the contents of a directory what you get back is a table of rows, where each row represents an item in that directory.
|
||||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||||
|
|
||||||
### Pipelines
|
### Pipelines
|
||||||
|
|
||||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||||
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
As in the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
||||||
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
||||||
Commands that work in the pipeline fit into one of three categories:
|
Commands that work in the pipeline fit into one of three categories:
|
||||||
|
|
||||||
- Commands that produce a stream (e.g., `ls`)
|
- Commands that produce a stream (e.g., `ls`)
|
||||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||||
- Commands that consume the output of the pipeline (e.g., `autoview`)
|
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||||
|
|
||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "Dir" | autoview
|
> ls | where type == "dir" | table
|
||||||
───┬────────┬──────┬───────┬──────────────
|
╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||||
# │ name │ type │ size │ modified
|
│ # │ name │ type │ size │ modified │
|
||||||
───┼────────┼──────┼───────┼──────────────
|
├────┼──────────┼──────┼─────────┼───────────────┤
|
||||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
3 │ docs │ Dir │ 192 B │ 50 mins ago
|
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||||
4 │ images │ Dir │ 160 B │ 5 months ago
|
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||||
5 │ src │ Dir │ 128 B │ 1 day ago
|
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||||
6 │ target │ Dir │ 160 B │ 5 days ago
|
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||||
7 │ tests │ Dir │ 192 B │ 3 months ago
|
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||||
───┴────────┴──────┴───────┴──────────────
|
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
|
│ 9 │ target │ dir │ 0 B │ a day ago │
|
||||||
|
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
|
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||||
|
╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
||||||
We could have also written the above:
|
We could have also written the above:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == Dir
|
> ls | where type == "dir"
|
||||||
```
|
```
|
||||||
|
|
||||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||||
@ -137,15 +108,13 @@ For example, we could use the built-in `ps` command to get a list of the running
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ps | where cpu > 0
|
> ps | where cpu > 0
|
||||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
│ # │ pid │ name │ cpu │ mem │ virtual │
|
||||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||||
0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
|
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||||
1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
|
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||||
2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
|
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||||
3 │ 2202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
|
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||||
4 │ 328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
|
|
||||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opening files
|
### Opening files
|
||||||
@ -155,72 +124,49 @@ For example, you can load a .toml file as structured data and explore it:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml
|
> open Cargo.toml
|
||||||
────────────────────┬───────────────────────────
|
╭──────────────────┬────────────────────╮
|
||||||
bin │ [table 18 rows]
|
│ bin │ [table 1 row] │
|
||||||
build-dependencies │ [row serde toml]
|
│ dependencies │ {record 24 fields} │
|
||||||
dependencies │ [row 29 columns]
|
│ dev-dependencies │ {record 8 fields} │
|
||||||
dev-dependencies │ [row nu-test-support]
|
│ features │ {record 10 fields} │
|
||||||
features │ [row 19 columns]
|
│ package │ {record 13 fields} │
|
||||||
package │ [row 12 columns]
|
│ profile │ {record 3 fields} │
|
||||||
workspace │ [row members]
|
│ target │ {record 2 fields} │
|
||||||
────────────────────┴───────────────────────────
|
│ workspace │ {record 1 field} │
|
||||||
|
╰──────────────────┴────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
We can pipeline this into a command that gets the contents of one of the columns:
|
We can pipe this into a command that gets the contents of one of the columns:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package
|
> open Cargo.toml | get package
|
||||||
───────────────┬────────────────────────────────────
|
╭───────────────┬────────────────────────────────────╮
|
||||||
authors │ [table 1 rows]
|
│ authors │ [list 1 item] │
|
||||||
default-run │ nu
|
│ default-run │ nu │
|
||||||
description │ A new type of shell
|
│ description │ A new type of shell │
|
||||||
documentation │ https://www.nushell.sh/book/
|
│ documentation │ https://www.nushell.sh/book/ │
|
||||||
edition │ 2018
|
│ edition │ 2018 │
|
||||||
exclude │ [table 1 rows]
|
│ exclude │ [list 1 item] │
|
||||||
homepage │ https://www.nushell.sh
|
│ homepage │ https://www.nushell.sh │
|
||||||
license │ MIT
|
│ license │ MIT │
|
||||||
name │ nu
|
│ name │ nu │
|
||||||
readme │ README.md
|
│ readme │ README.md │
|
||||||
repository │ https://github.com/nushell/nushell
|
│ repository │ https://github.com/nushell/nushell │
|
||||||
version │ 0.32.0
|
│ rust-version │ 1.60 │
|
||||||
───────────────┴────────────────────────────────────
|
│ version │ 0.63.1 │
|
||||||
|
╰───────────────┴────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we can use commands outside of Nu once we have the data we want:
|
And if needed we can drill down further:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version
|
> open Cargo.toml | get package.version
|
||||||
0.32.0
|
0.63.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
|
|
||||||
|
|
||||||
To set one of these variables, you can use `config set`. For example:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
> config set line_editor.edit_mode "vi"
|
|
||||||
> config set path $nu.path
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shells
|
|
||||||
|
|
||||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
|
||||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
|
|
||||||
|
|
||||||
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
|
|
||||||
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
|
|
||||||
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
|
|
||||||
|
|
||||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
|
||||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. There are a few examples in the `crates/nu_plugins_*` directories.
|
||||||
This allows you to extend nu for your needs.
|
|
||||||
|
|
||||||
There are a few examples in the `plugins` directory.
|
|
||||||
|
|
||||||
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
|
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
|
||||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
|
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
|
||||||
@ -231,23 +177,19 @@ If the plugin is a sink, it is given the full vector of final data and is given
|
|||||||
|
|
||||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||||
|
|
||||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer consistent first-class support for Windows, macOS, and Linux.
|
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has first-class support for Windows, macOS, and Linux.
|
||||||
|
|
||||||
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
- Nu ensures compatibility with existing platform-specific executables.
|
||||||
|
|
||||||
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
- Nu's workflow and tools should have the usability expected of modern software in 2022 (and beyond).
|
||||||
|
|
||||||
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
- Nu views data as either structured or unstructured. It is a structured shell like PowerShell.
|
||||||
|
|
||||||
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
|
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
Nu is under heavy development and will naturally change as it matures. The chart below isn't meant to be exhaustive, but it helps give an idea for some of the areas of development and their relative maturity:
|
||||||
|
|
||||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||||
@ -270,20 +212,15 @@ Nu is in heavy development and will naturally change as it matures and people us
|
|||||||
|
|
||||||
Please submit an issue or PR to be added to this list.
|
Please submit an issue or PR to be added to this list.
|
||||||
|
|
||||||
### Integrations
|
|
||||||
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
||||||
- [starship](https://github.com/starship/starship)
|
- [starship](https://github.com/starship/starship)
|
||||||
- [oh-my-posh](https://ohmyposh.dev)
|
- [oh-my-posh](https://ohmyposh.dev)
|
||||||
- [Couchbase Shell](https://couchbase.sh)
|
- [Couchbase Shell](https://couchbase.sh)
|
||||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||||
### Mentions
|
|
||||||
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](CONTRIBUTING.md) for details.
|
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||||
|
|
||||||
Thanks to all the people who already contributed!
|
|
||||||
|
|
||||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||||
|
|
||||||
> register -e json ./nu_plugin_query
|
> register ./nu_plugin_query
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 7.3 KiB |
@ -10,6 +10,7 @@ NU_PLUGINS=(
|
|||||||
'nu_plugin_gstat'
|
'nu_plugin_gstat'
|
||||||
'nu_plugin_inc'
|
'nu_plugin_inc'
|
||||||
'nu_plugin_query'
|
'nu_plugin_query'
|
||||||
|
'nu_plugin_custom_values'
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Building nushell"
|
echo "Building nushell"
|
||||||
|
@ -24,9 +24,13 @@ cargo build
|
|||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd ..\..\crates\nu_plugin_query
|
@cd ..\..\crates\nu_plugin_query
|
||||||
|
|
||||||
echo Building nu_plugin_query.exe
|
echo Building nu_plugin_query.exe
|
||||||
cargo build
|
cargo build
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
|
@cd ..\..\crates\nu_plugin_custom_values
|
||||||
|
echo Building nu_plugin_custom_values.exe
|
||||||
|
cargo build
|
||||||
|
@echo.
|
||||||
|
|
||||||
@cd ..\..
|
@cd ..\..
|
@ -11,6 +11,7 @@ let plugins = [
|
|||||||
nu_plugin_gstat,
|
nu_plugin_gstat,
|
||||||
nu_plugin_query,
|
nu_plugin_query,
|
||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
|
nu_plugin_custom_values,
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
@ -1,31 +1,39 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "CLI-related functionality for Nushell"
|
description = "CLI-related functionality for Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.63.1"
|
version = "0.71.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.63.1" }
|
nu-test-support = { path="../nu-test-support", version = "0.71.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.63.1" }
|
nu-command = { path = "../nu-command", version = "0.71.0" }
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.63.1" }
|
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.63.1" }
|
nu-path = { path = "../nu-path", version = "0.71.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.63.1" }
|
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.63.1" }
|
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.63.1" }
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
crossterm = "0.23.0"
|
|
||||||
miette = { version = "4.5.0", features = ["fancy"] }
|
|
||||||
thiserror = "1.0.29"
|
|
||||||
fuzzy-matcher = "0.3.7"
|
|
||||||
|
|
||||||
log = "0.4"
|
atty = "0.2.14"
|
||||||
|
chrono = "0.4.21"
|
||||||
|
crossterm = "0.24.0"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
|
fuzzy-matcher = "0.3.7"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
log = "0.4"
|
||||||
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
|
percent-encoding = "2"
|
||||||
|
sysinfo = "0.26.2"
|
||||||
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -5,21 +5,26 @@ use nu_engine::{convert_env_values, eval_block};
|
|||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::Stack;
|
use nu_protocol::engine::Stack;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
PipelineData, Spanned, Value,
|
PipelineData, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
init_cwd: &Path,
|
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
table_mode: Option<Value>,
|
table_mode: Option<Value>,
|
||||||
) -> Result<()> {
|
) -> Result<Option<i64>> {
|
||||||
// Run a command (or commands) given to us by the user
|
// Translate environment variables from Strings to Values
|
||||||
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source code
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
@ -39,43 +44,19 @@ pub fn evaluate_commands(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
// Update permanent state
|
||||||
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = engine_state.get_config().clone();
|
// Run the block
|
||||||
if let Some(t_mode) = table_mode {
|
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
config.table_mode = t_mode.as_string()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
|
||||||
Ok(cwd) => {
|
|
||||||
if let Err(e) =
|
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
|
let mut config = engine_state.get_config().clone();
|
||||||
|
if let Some(t_mode) = table_mode {
|
||||||
|
config.table_mode = t_mode.as_string()?;
|
||||||
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -84,11 +65,9 @@ pub fn evaluate_commands(
|
|||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(exit_code)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
|||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandCompletion {
|
impl CommandCompletion {
|
||||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
|||||||
_: &StateWorkingSet,
|
_: &StateWorkingSet,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
flattened,
|
flattened,
|
||||||
flat_shape,
|
flat_shape,
|
||||||
|
force_completion_after_space,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,19 +46,21 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if !executables.contains(
|
if self.engine_state.config.max_external_completion_results
|
||||||
&item
|
> executables.len() as i64
|
||||||
.path()
|
&& !executables.contains(
|
||||||
.file_name()
|
&item
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
.path()
|
||||||
.unwrap_or_default(),
|
.file_name()
|
||||||
) && matches!(
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
item.path()
|
.unwrap_or_default(),
|
||||||
.file_name()
|
)
|
||||||
.map(|x| match_algorithm
|
&& matches!(
|
||||||
|
item.path().file_name().map(|x| match_algorithm
|
||||||
.matches_str(&x.to_string_lossy(), prefix)),
|
.matches_str(&x.to_string_lossy(), prefix)),
|
||||||
Some(true)
|
Some(true)
|
||||||
) && is_executable::is_executable(&item.path())
|
)
|
||||||
|
&& is_executable::is_executable(item.path())
|
||||||
{
|
{
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
executables.push(name);
|
executables.push(name);
|
||||||
@ -114,7 +119,8 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
let partial = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
let partial = String::from_utf8_lossy(partial).to_string();
|
let partial = String::from_utf8_lossy(partial).to_string();
|
||||||
let results = if find_externals {
|
|
||||||
|
if find_externals {
|
||||||
let results_external = self
|
let results_external = self
|
||||||
.external_command_completion(&partial, match_algorithm)
|
.external_command_completion(&partial, match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -146,9 +152,7 @@ impl CommandCompletion {
|
|||||||
results
|
results
|
||||||
} else {
|
} else {
|
||||||
results
|
results
|
||||||
};
|
}
|
||||||
|
|
||||||
results
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,12 +203,23 @@ impl Completer for CommandCompletion {
|
|||||||
return subcommands;
|
return subcommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let config = working_set.get_config();
|
||||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||||
|| ((span.end - span.start) == 0)
|
|| ((span.end - span.start) == 0)
|
||||||
{
|
{
|
||||||
// we're in a gap or at a command
|
// we're in a gap or at a command
|
||||||
self.complete_commands(working_set, span, offset, true, options.match_algorithm)
|
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||||
|
{
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
self.complete_commands(
|
||||||
|
working_set,
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
|
config.enable_external_completion,
|
||||||
|
options.match_algorithm,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,11 @@ use crate::completions::{
|
|||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||||
};
|
};
|
||||||
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
BlockId, PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||||
use std::str;
|
use std::str;
|
||||||
@ -56,64 +57,123 @@ impl NuCompleter {
|
|||||||
suggestions
|
suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn external_completion(
|
||||||
|
&self,
|
||||||
|
block_id: BlockId,
|
||||||
|
spans: &[String],
|
||||||
|
offset: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Option<Vec<Suggestion>> {
|
||||||
|
let stack = self.stack.clone();
|
||||||
|
let block = self.engine_state.get_block(block_id);
|
||||||
|
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
|
// Line
|
||||||
|
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
||||||
|
if let Some(var_id) = pos_arg.var_id {
|
||||||
|
callee_stack.add_var(
|
||||||
|
var_id,
|
||||||
|
Value::List {
|
||||||
|
vals: spans
|
||||||
|
.iter()
|
||||||
|
.map(|it| Value::String {
|
||||||
|
val: it.to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = eval_block(
|
||||||
|
&self.engine_state,
|
||||||
|
&mut callee_stack,
|
||||||
|
block,
|
||||||
|
PipelineData::new(span),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(pd) => {
|
||||||
|
let value = pd.into_value(span);
|
||||||
|
if let Value::List { vals, span: _ } = value {
|
||||||
|
let result = map_value_completions(
|
||||||
|
vals.iter(),
|
||||||
|
Span {
|
||||||
|
start: span.start,
|
||||||
|
end: span.end,
|
||||||
|
},
|
||||||
|
offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => println!("failed to eval completer block: {}", err),
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||||
let initial_line = line.to_string();
|
let initial_line = line.to_string();
|
||||||
let mut line = line.to_string();
|
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||||
line.insert(pos, 'a');
|
new_line.insert(alias_total_offset + pos, b'a');
|
||||||
let pos = offset + pos;
|
let pos = offset + pos;
|
||||||
let (output, _err) = parse(
|
let config = self.engine_state.get_config();
|
||||||
&mut working_set,
|
|
||||||
Some("completer"),
|
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||||
line.as_bytes(),
|
|
||||||
false,
|
|
||||||
&[],
|
|
||||||
);
|
|
||||||
|
|
||||||
for pipeline in output.pipelines.into_iter() {
|
for pipeline in output.pipelines.into_iter() {
|
||||||
for expr in pipeline.expressions {
|
for expr in pipeline.expressions {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
|
let span_offset: usize = alias_offset.iter().sum();
|
||||||
|
let mut spans: Vec<String> = vec![];
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
if pos >= flat.0.start && pos < flat.0.end {
|
// Read the current spam to string
|
||||||
|
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||||
|
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||||
|
|
||||||
|
// Skip the last 'a' as span item
|
||||||
|
if flat_idx == flattened.len() - 1 {
|
||||||
|
let mut chars = current_span_str.chars();
|
||||||
|
chars.next_back();
|
||||||
|
let current_span_str = chars.as_str().to_owned();
|
||||||
|
spans.push(current_span_str.to_string());
|
||||||
|
} else {
|
||||||
|
spans.push(current_span_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete based on the last span
|
||||||
|
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||||
// Context variables
|
// Context variables
|
||||||
let most_left_var =
|
let most_left_var =
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
|
|
||||||
// Create a new span
|
// Create a new span
|
||||||
let new_span = Span {
|
let new_span = if flat_idx == 0 {
|
||||||
start: flat.0.start,
|
Span {
|
||||||
end: flat.0.end - 1,
|
start: flat.0.start,
|
||||||
|
end: flat.0.end - 1 - span_offset,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Span {
|
||||||
|
start: flat.0.start - span_offset,
|
||||||
|
end: flat.0.end - 1 - span_offset,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses the prefix
|
// 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 mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
prefix.remove(pos - flat.0.start);
|
let index = pos - (flat.0.start - span_offset);
|
||||||
|
prefix.drain(index..);
|
||||||
// Completions that depends on the previous expression (e.g: use, source)
|
|
||||||
if flat_idx > 0 {
|
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
|
||||||
// Read the content for the previous expression
|
|
||||||
let prev_expr_str =
|
|
||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
|
||||||
|
|
||||||
// Completion for .nu files
|
|
||||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
|
||||||
let mut completer =
|
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables completion
|
// Variables completion
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
@ -135,8 +195,42 @@ impl NuCompleter {
|
|||||||
|
|
||||||
// Flags completion
|
// Flags completion
|
||||||
if prefix.starts_with(b"-") {
|
if prefix.starts_with(b"-") {
|
||||||
let mut completer = FlagCompletion::new(expr);
|
// Try to complete flag internally
|
||||||
|
let mut completer = FlagCompletion::new(expr.clone());
|
||||||
|
let result = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got no results for internal completion
|
||||||
|
// now we can check if external completer is set and use it
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) =
|
||||||
|
self.external_completion(block_id, &spans, offset, new_span)
|
||||||
|
{
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// specially check if it is currently empty - always complete commands
|
||||||
|
if flat_idx == 0 && working_set.get_span_contents(new_span).is_empty() {
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
FlatShape::String,
|
||||||
|
true,
|
||||||
|
);
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -147,6 +241,42 @@ impl NuCompleter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||||
|
if flat_idx > 0 {
|
||||||
|
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||||
|
// Read the content for the previous expression
|
||||||
|
let prev_expr_str =
|
||||||
|
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||||
|
|
||||||
|
// Completion for .nu files
|
||||||
|
if prev_expr_str == b"use" || prev_expr_str == b"source-env" {
|
||||||
|
let mut completer =
|
||||||
|
DotNuCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
} else if prev_expr_str == b"ls" {
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match other types
|
// Match other types
|
||||||
match &flat.1 {
|
match &flat.1 {
|
||||||
FlatShape::Custom(decl_id) => {
|
FlatShape::Custom(decl_id) => {
|
||||||
@ -179,6 +309,18 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
|
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
flat_shape => {
|
flat_shape => {
|
||||||
let mut completer = CommandCompletion::new(
|
let mut completer = CommandCompletion::new(
|
||||||
self.engine_state.clone(),
|
self.engine_state.clone(),
|
||||||
@ -186,9 +328,10 @@ impl NuCompleter {
|
|||||||
flattened.clone(),
|
flattened.clone(),
|
||||||
// flat_idx,
|
// flat_idx,
|
||||||
flat_shape.clone(),
|
flat_shape.clone(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let out: Vec<_> = self.process_completion(
|
let mut out: Vec<_> = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
prefix.clone(),
|
prefix.clone(),
|
||||||
@ -197,21 +340,33 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
|
||||||
if out.is_empty() {
|
if !out.is_empty() {
|
||||||
let mut completer =
|
return out;
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
// Try to complete using an external completer (if set)
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) =
|
||||||
|
self.external_completion(block_id, &spans, offset, new_span)
|
||||||
|
{
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for file completion
|
||||||
|
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||||
|
out = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -219,7 +374,7 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return vec![];
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +384,85 @@ impl ReedlineCompleter for NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||||
|
|
||||||
|
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||||
|
// that `g` is an alias of `git`
|
||||||
|
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
|
||||||
|
// An vector represents the offsets of alias
|
||||||
|
// e.g: the offset is 2 for the alias `g` of `git`
|
||||||
|
let mut alias_offset = vec![];
|
||||||
|
let mut output = vec![];
|
||||||
|
if let Some(matched_alias) = search_alias(line, working_set) {
|
||||||
|
let mut lens = matched_alias.len();
|
||||||
|
for (input_vec, line_vec) in matched_alias {
|
||||||
|
alias_offset.push(line_vec.len() - input_vec.len());
|
||||||
|
output.extend(line_vec);
|
||||||
|
if lens > 1 {
|
||||||
|
output.push(b' ');
|
||||||
|
lens -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !line.is_empty() {
|
||||||
|
let last = line.last().expect("input is empty");
|
||||||
|
if last == &b' ' {
|
||||||
|
output.push(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output = line.to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, alias_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||||
|
let mut vec_names = vec![];
|
||||||
|
let mut vec_alias = vec![];
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut is_alias = false;
|
||||||
|
for (index, character) in input.iter().enumerate() {
|
||||||
|
if *character == b' ' {
|
||||||
|
let range = &input[pos..index];
|
||||||
|
vec_names.push(range.to_owned());
|
||||||
|
pos = index + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Push the rest to names vector.
|
||||||
|
if pos < input.len() {
|
||||||
|
vec_names.push(input[pos..].to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
for name in &vec_names {
|
||||||
|
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||||
|
let alias_span = working_set.get_alias(alias_id);
|
||||||
|
let mut span_vec = vec![];
|
||||||
|
is_alias = true;
|
||||||
|
for alias in alias_span {
|
||||||
|
let name = working_set.get_span_contents(*alias);
|
||||||
|
if !name.is_empty() {
|
||||||
|
span_vec.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
|
||||||
|
let full_aliases = span_vec.join(&[b' '][..]);
|
||||||
|
vec_alias.push(full_aliases);
|
||||||
|
} else {
|
||||||
|
vec_alias.push(name.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_alias {
|
||||||
|
// Zip names and alias vectors, the original inputs and its aliases mapping.
|
||||||
|
// e.g:(['g'], ['g','i','t'])
|
||||||
|
let output = vec_names.into_iter().zip(vec_alias).collect();
|
||||||
|
Some(output)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reads the most left variable returning it's name (e.g: $myvar)
|
// reads the most left variable returning it's name (e.g: $myvar)
|
||||||
// and the depth (a.b.c)
|
// and the depth (a.b.c)
|
||||||
fn most_left_variable(
|
fn most_left_variable(
|
||||||
@ -278,3 +512,65 @@ fn most_left_variable(
|
|||||||
|
|
||||||
Some((var, sublevels))
|
Some((var, sublevels))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_value_completions<'a>(
|
||||||
|
list: impl Iterator<Item = &'a Value>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
|
list.filter_map(move |x| {
|
||||||
|
// Match for string values
|
||||||
|
if let Ok(s) = x.as_string() {
|
||||||
|
return Some(Suggestion {
|
||||||
|
value: s,
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match for record values
|
||||||
|
if let Ok((cols, vals)) = x.as_record() {
|
||||||
|
let mut suggestion = Suggestion {
|
||||||
|
value: String::from(""), // Initialize with empty string
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate the cols looking for `value` and `description`
|
||||||
|
cols.iter().zip(vals).for_each(|it| {
|
||||||
|
// Match `value` column
|
||||||
|
if it.0 == "value" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(val_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.value = val_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match `description` column
|
||||||
|
if it.0 == "description" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(desc_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.description = Some(desc_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Some(suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::completer::map_value_completions;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
|||||||
sort_by: SortBy::None,
|
sort_by: SortBy::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_completions<'a>(
|
|
||||||
&self,
|
|
||||||
list: impl Iterator<Item = &'a Value>,
|
|
||||||
span: Span,
|
|
||||||
offset: usize,
|
|
||||||
) -> Vec<Suggestion> {
|
|
||||||
list.filter_map(move |x| {
|
|
||||||
// Match for string values
|
|
||||||
if let Ok(s) = x.as_string() {
|
|
||||||
return Some(Suggestion {
|
|
||||||
value: s,
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match for record values
|
|
||||||
if let Ok((cols, vals)) = x.as_record() {
|
|
||||||
let mut suggestion = Suggestion {
|
|
||||||
value: String::from(""), // Initialize with empty string
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Iterate the cols looking for `value` and `description`
|
|
||||||
cols.iter().zip(vals).for_each(|it| {
|
|
||||||
// Match `value` column
|
|
||||||
if it.0 == "value" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(val_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.value = val_str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match `description` column
|
|
||||||
if it.0 == "description" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(desc_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.description = Some(desc_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Some(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CustomCompletion {
|
impl Completer for CustomCompletion {
|
||||||
@ -144,7 +83,7 @@ impl Completer for CustomCompletion {
|
|||||||
.and_then(|val| {
|
.and_then(|val| {
|
||||||
val.as_list()
|
val.as_list()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let options = value.get_data_by_key("options");
|
let options = value.get_data_by_key("options");
|
||||||
@ -189,7 +128,7 @@ impl Completer for CustomCompletion {
|
|||||||
|
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
|
|||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
|
@ -1,27 +1,36 @@
|
|||||||
use crate::util::{eval_source, report_error};
|
use crate::util::{eval_source, report_error};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use log::info;
|
use log::info;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
#[cfg(feature = "plugin")]
|
||||||
use nu_protocol::{PipelineData, Span};
|
use nu_parser::ParseError;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_path::canonicalize_with;
|
||||||
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_protocol::Spanned;
|
||||||
|
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
const PLUGIN_FILE: &str = "plugin.nu";
|
const PLUGIN_FILE: &str = "plugin.nu";
|
||||||
|
|
||||||
|
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||||
|
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(
|
pub fn read_plugin_file(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
is_perf_true: bool,
|
|
||||||
) {
|
) {
|
||||||
// Reading signatures from signature file
|
// Reading signatures from signature file
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, storage_path);
|
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
if let Some(plugin_path) = plugin_path {
|
if let Some(plugin_path) = plugin_path {
|
||||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
let plugin_filename = plugin_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -34,14 +43,27 @@ pub fn read_plugin_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
pub fn add_plugin_file(
|
||||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
engine_state: &mut EngineState,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
|
storage_path: &str,
|
||||||
|
) {
|
||||||
|
if let Some(plugin_file) = plugin_file {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
match canonicalize_with(&plugin_file.item, cwd) {
|
||||||
|
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||||
|
Err(_) => {
|
||||||
|
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
@ -55,7 +77,7 @@ pub fn eval_config_contents(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) {
|
) {
|
||||||
if config_path.exists() & config_path.is_file() {
|
if config_path.exists() & config_path.is_file() {
|
||||||
let config_filename = config_path.to_string_lossy().to_owned();
|
let config_filename = config_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&config_path) {
|
if let Ok(contents) = std::fs::read(&config_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -66,12 +88,10 @@ pub fn eval_config_contents(
|
|||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::new(Span::new(0, 0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
if let Err(e) =
|
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
@ -84,3 +104,14 @@ pub fn eval_config_contents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
|
||||||
|
nu_path::config_dir().map(|mut history_path| {
|
||||||
|
history_path.push(storage_path);
|
||||||
|
history_path.push(match mode {
|
||||||
|
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
|
||||||
|
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
||||||
|
});
|
||||||
|
history_path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ use log::trace;
|
|||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_engine::convert_env_values;
|
use nu_engine::convert_env_values;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::Type;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
@ -18,7 +19,6 @@ pub fn evaluate_file(
|
|||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
@ -34,7 +34,7 @@ pub fn evaluate_file(
|
|||||||
|
|
||||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
||||||
|
|
||||||
if working_set.find_decl(b"main").is_some() {
|
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||||
let args = format!("main {}", args.join(" "));
|
let args = format!("main {}", args.join(" "));
|
||||||
|
|
||||||
if !eval_source(
|
if !eval_source(
|
||||||
@ -53,9 +53,7 @@ pub fn evaluate_file(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ pub fn print_table_or_error(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
mut pipeline_data: PipelineData,
|
mut pipeline_data: PipelineData,
|
||||||
config: &mut Config,
|
config: &mut Config,
|
||||||
) {
|
) -> Option<i64> {
|
||||||
let exit_code = match &mut pipeline_data {
|
let exit_code = match &mut pipeline_data {
|
||||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -76,59 +74,63 @@ pub fn print_table_or_error(
|
|||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Some(decl_id) => {
|
Some(decl_id) => {
|
||||||
let table = engine_state.get_decl(decl_id).run(
|
let command = engine_state.get_decl(decl_id);
|
||||||
engine_state,
|
if command.get_block_id().is_some() {
|
||||||
stack,
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
&Call::new(Span::new(0, 0)),
|
} else {
|
||||||
pipeline_data,
|
let table = command.run(
|
||||||
);
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&Call::new(Span::new(0, 0)),
|
||||||
|
pipeline_data,
|
||||||
|
);
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
for item in table {
|
print_or_exit(table, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
}
|
||||||
}
|
Err(error) => {
|
||||||
Err(error) => {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
for item in pipeline_data {
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure everything has finished
|
// Make sure everything has finished
|
||||||
if let Some(exit_code) = exit_code {
|
if let Some(exit_code) = exit_code {
|
||||||
let _: Vec<_> = exit_code.into_iter().collect();
|
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
|
||||||
|
exit_code
|
||||||
|
.pop()
|
||||||
|
.and_then(|last_exit_code| match last_exit_code {
|
||||||
|
Value::Int { val: code, .. } => Some(code),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||||
|
for item in pipeline_data {
|
||||||
|
if let Value::Error { error } = item {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = item.into_string("\n", config);
|
||||||
|
out.push('\n');
|
||||||
|
|
||||||
|
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
|
|||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
pub use repl::evaluate_repl;
|
pub use repl::evaluate_repl;
|
||||||
|
pub use repl::{eval_env_change_hook, eval_hook};
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
nu_ansi_term::{ansi::RESET, Style},
|
nu_ansi_term::{ansi::RESET, Style},
|
||||||
reedline::{
|
reedline::{
|
||||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||||
Painter, Suggestion,
|
Painter, Suggestion, UndoBehavior,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ impl DescriptionMenu {
|
|||||||
let description = self
|
let description = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.skip(self.skipped_rows)
|
.skip(self.skipped_rows)
|
||||||
.take(self.working_details.description_rows)
|
.take(self.working_details.description_rows)
|
||||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
|||||||
fn can_partially_complete(
|
fn can_partially_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
_values_updated: bool,
|
_values_updated: bool,
|
||||||
_line_buffer: &mut LineBuffer,
|
_editor: &mut Editor,
|
||||||
_completer: &mut dyn Completer,
|
_completer: &mut dyn Completer,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates menu values
|
/// Updates menu values
|
||||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
||||||
if self.only_buffer_difference {
|
if self.only_buffer_difference {
|
||||||
if let Some(old_string) = &self.input {
|
if let Some(old_string) = &self.input {
|
||||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.values = completer.complete(input, start);
|
self.values = completer.complete(input, start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||||
self.values =
|
self.values = completer.complete(
|
||||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
trimmed_buffer.as_str(),
|
||||||
|
editor.line_buffer().insertion_point(),
|
||||||
|
);
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
|||||||
/// collected from the completer
|
/// collected from the completer
|
||||||
fn update_working_details(
|
fn update_working_details(
|
||||||
&mut self,
|
&mut self,
|
||||||
line_buffer: &mut LineBuffer,
|
editor: &mut Editor,
|
||||||
completer: &mut dyn Completer,
|
completer: &mut dyn Completer,
|
||||||
painter: &Painter,
|
painter: &Painter,
|
||||||
) {
|
) {
|
||||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
|||||||
match event {
|
match event {
|
||||||
MenuEvent::Activate(_) => {
|
MenuEvent::Activate(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.input = Some(line_buffer.get_buffer().to_string());
|
self.input = Some(editor.get_buffer().to_string());
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
}
|
}
|
||||||
MenuEvent::Deactivate => self.active = false,
|
MenuEvent::Deactivate => self.active = false,
|
||||||
MenuEvent::Edit(_) => {
|
MenuEvent::Edit(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
self.update_examples()
|
self.update_examples()
|
||||||
}
|
}
|
||||||
MenuEvent::NextElement => {
|
MenuEvent::NextElement => {
|
||||||
@ -608,7 +610,7 @@ impl Menu for DescriptionMenu {
|
|||||||
let description_rows = self
|
let description_rows = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The buffer gets replaced in the Span location
|
/// The buffer gets replaced in the Span location
|
||||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
fn replace_in_buffer(&self, editor: &mut Editor) {
|
||||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||||
let start = span.start.min(line_buffer.len());
|
let start = span.start.min(editor.line_buffer().len());
|
||||||
let end = span.end.min(line_buffer.len());
|
let end = span.end.min(editor.line_buffer().len());
|
||||||
|
|
||||||
let string_len = if let Some(example_index) = self.example_index {
|
let replacement = if let Some(example_index) = self.example_index {
|
||||||
let example = self
|
self.examples
|
||||||
.examples
|
|
||||||
.get(example_index)
|
.get(example_index)
|
||||||
.expect("the example index is always checked");
|
.expect("the example index is always checked")
|
||||||
|
|
||||||
line_buffer.replace(start..end, example);
|
|
||||||
example.len()
|
|
||||||
} else {
|
} else {
|
||||||
line_buffer.replace(start..end, &value);
|
&value
|
||||||
value.len()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = line_buffer.insertion_point();
|
editor.edit_buffer(
|
||||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
|lb| {
|
||||||
line_buffer.set_insertion_point(offset);
|
lb.replace_range(start..end, replacement);
|
||||||
|
let mut offset = lb.insertion_point();
|
||||||
|
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||||
|
lb.set_insertion_point(offset);
|
||||||
|
},
|
||||||
|
UndoBehavior::CreateUndoPoint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use nu_engine::documentation::get_flags_section;
|
use nu_engine::documentation::get_flags_section;
|
||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||||
@ -19,6 +20,10 @@ impl NuHelpCompleter {
|
|||||||
.filter(|(sig, _, _, _)| {
|
.filter(|(sig, _, _, _)| {
|
||||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||||
|
|| sig
|
||||||
|
.search_terms
|
||||||
|
.iter()
|
||||||
|
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
||||||
|| sig
|
|| sig
|
||||||
.extra_usage
|
.extra_usage
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
@ -49,7 +54,7 @@ impl NuHelpCompleter {
|
|||||||
long_desc.push_str("\r\n\r\n");
|
long_desc.push_str("\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
long_desc.push_str(&format!("Usage:\r\n > {}\r\n", sig.call_signature()));
|
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(sig))
|
long_desc.push_str(&get_flags_section(sig))
|
||||||
@ -61,27 +66,28 @@ impl NuHelpCompleter {
|
|||||||
{
|
{
|
||||||
long_desc.push_str("\r\nParameters:\r\n");
|
long_desc.push_str("\r\nParameters:\r\n");
|
||||||
for positional in &sig.required_positional {
|
for positional in &sig.required_positional {
|
||||||
long_desc
|
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||||
.push_str(&format!(" {}: {}\r\n", positional.name, positional.desc));
|
|
||||||
}
|
}
|
||||||
for positional in &sig.optional_positional {
|
for positional in &sig.optional_positional {
|
||||||
long_desc.push_str(&format!(
|
let _ = write!(
|
||||||
|
long_desc,
|
||||||
" (optional) {}: {}\r\n",
|
" (optional) {}: {}\r\n",
|
||||||
positional.name, positional.desc
|
positional.name, positional.desc
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rest_positional) = &sig.rest_positional {
|
if let Some(rest_positional) = &sig.rest_positional {
|
||||||
long_desc.push_str(&format!(
|
let _ = write!(
|
||||||
|
long_desc,
|
||||||
" ...{}: {}\r\n",
|
" ...{}: {}\r\n",
|
||||||
rest_positional.name, rest_positional.desc
|
rest_positional.name, rest_positional.desc
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let extra: Vec<String> = examples
|
let extra: Vec<String> = examples
|
||||||
.iter()
|
.iter()
|
||||||
.map(|example| example.example.to_string())
|
.map(|example| example.example.replace('\n', "\r\n"))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Suggestion {
|
Suggestion {
|
||||||
|
@ -19,6 +19,10 @@ impl Command for NuHighlight {
|
|||||||
"Syntax highlight the input string."
|
"Syntax highlight the input string."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["syntax", "color", "convert"]
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -17,15 +17,27 @@ impl Command for Print {
|
|||||||
Signature::build("print")
|
Signature::build("print")
|
||||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||||
.switch(
|
.switch(
|
||||||
"no_newline",
|
"no-newline",
|
||||||
"print without inserting a newline for the line ending",
|
"print without inserting a newline for the line ending",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
|
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Prints the values given"
|
"Print the given values to stdout"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||||
|
Since this command has no output, there is no point in piping it with other commands.
|
||||||
|
|
||||||
|
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["display"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -36,12 +48,13 @@ impl Command for Print {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag("no_newline");
|
let no_newline = call.has_flag("no-newline");
|
||||||
|
let to_stderr = call.has_flag("stderr");
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg.into_pipeline_data()
|
arg.into_pipeline_data()
|
||||||
.print(engine_state, stack, no_newline)?;
|
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::new(head))
|
Ok(PipelineData::new(head))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::DefaultPrompt;
|
use reedline::DefaultPrompt;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
reedline::{
|
reedline::{
|
||||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||||
@ -16,6 +17,7 @@ pub struct NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: Option<String>,
|
default_vi_insert_prompt_indicator: Option<String>,
|
||||||
default_vi_normal_prompt_indicator: Option<String>,
|
default_vi_normal_prompt_indicator: Option<String>,
|
||||||
default_multiline_indicator: Option<String>,
|
default_multiline_indicator: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NushellPrompt {
|
impl Default for NushellPrompt {
|
||||||
@ -33,6 +35,7 @@ impl NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: None,
|
default_vi_insert_prompt_indicator: None,
|
||||||
default_vi_normal_prompt_indicator: None,
|
default_vi_normal_prompt_indicator: None,
|
||||||
default_multiline_indicator: None,
|
default_multiline_indicator: None,
|
||||||
|
render_right_prompt_on_last_line: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +43,13 @@ impl NushellPrompt {
|
|||||||
self.left_prompt_string = prompt_string;
|
self.left_prompt_string = prompt_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
pub fn update_prompt_right(
|
||||||
|
&mut self,
|
||||||
|
prompt_string: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
|
) {
|
||||||
self.right_prompt_string = prompt_string;
|
self.right_prompt_string = prompt_string;
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||||
@ -67,6 +75,7 @@ impl NushellPrompt {
|
|||||||
prompt_indicator_string: Option<String>,
|
prompt_indicator_string: Option<String>,
|
||||||
prompt_multiline_indicator_string: Option<String>,
|
prompt_multiline_indicator_string: Option<String>,
|
||||||
prompt_vi: (Option<String>, Option<String>),
|
prompt_vi: (Option<String>, Option<String>),
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
) {
|
) {
|
||||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||||
|
|
||||||
@ -77,6 +86,8 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||||
|
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||||
@ -86,6 +97,11 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
impl Prompt for NushellPrompt {
|
impl Prompt for NushellPrompt {
|
||||||
fn render_prompt_left(&self) -> Cow<str> {
|
fn render_prompt_left(&self) -> Cow<str> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = enable_vt_processing();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(prompt_string) = &self.left_prompt_string {
|
if let Some(prompt_string) = &self.left_prompt_string {
|
||||||
prompt_string.replace('\n', "\r\n").into()
|
prompt_string.replace('\n', "\r\n").into()
|
||||||
} else {
|
} else {
|
||||||
@ -156,4 +172,8 @@ impl Prompt for NushellPrompt {
|
|||||||
prefix, history_search.term
|
prefix, history_search.term
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn right_prompt_on_last_line(&self) -> bool {
|
||||||
|
self.render_right_prompt_on_last_line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,16 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
|||||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||||
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
|
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||||
|
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||||
|
|
||||||
fn get_prompt_string(
|
fn get_prompt_string(
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
@ -40,14 +43,12 @@ fn get_prompt_string(
|
|||||||
block,
|
block,
|
||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||||
);
|
);
|
||||||
if is_perf_true {
|
info!(
|
||||||
info!(
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
file!(),
|
||||||
file!(),
|
line!(),
|
||||||
line!(),
|
column!()
|
||||||
column!()
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
match ret_val {
|
match ret_val {
|
||||||
Ok(ret_val) => Some(ret_val),
|
Ok(ret_val) => Some(ret_val),
|
||||||
@ -86,57 +87,39 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
nu_prompt: &'prompt mut NushellPrompt,
|
nu_prompt: &'prompt mut NushellPrompt,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> &'prompt dyn Prompt {
|
) -> &'prompt dyn Prompt {
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
||||||
PROMPT_COMMAND,
|
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let right_prompt_string = get_prompt_string(
|
// Now that we have the prompt string lets ansify it.
|
||||||
PROMPT_COMMAND_RIGHT,
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
config,
|
let left_prompt_string = if config.shell_integration {
|
||||||
engine_state,
|
match left_prompt_string {
|
||||||
&mut stack,
|
Some(prompt_string) => Some(format!(
|
||||||
is_perf_true,
|
"{}{}{}",
|
||||||
);
|
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||||
|
)),
|
||||||
|
None => left_prompt_string,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
left_prompt_string
|
||||||
|
};
|
||||||
|
|
||||||
let prompt_indicator_string = get_prompt_string(
|
let right_prompt_string =
|
||||||
PROMPT_INDICATOR,
|
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_multiline_string = get_prompt_string(
|
let prompt_indicator_string =
|
||||||
PROMPT_MULTILINE_INDICATOR,
|
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_insert_string = get_prompt_string(
|
let prompt_multiline_string =
|
||||||
PROMPT_INDICATOR_VI_INSERT,
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_normal_string = get_prompt_string(
|
let prompt_vi_insert_string =
|
||||||
PROMPT_INDICATOR_VI_NORMAL,
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
let prompt_vi_normal_string =
|
||||||
&mut stack,
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
@ -145,12 +128,11 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
prompt_indicator_string,
|
prompt_indicator_string,
|
||||||
prompt_multiline_string,
|
prompt_multiline_string,
|
||||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||||
|
config.render_right_prompt_on_last_line,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
let ret_val = nu_prompt as &dyn Prompt;
|
||||||
if is_perf_true {
|
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ pub(crate) fn add_menus(
|
|||||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
for menu in create_menus(&value, config)? {
|
for menu in create_menus(&value)? {
|
||||||
line_editor =
|
line_editor =
|
||||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||||
}
|
}
|
||||||
@ -491,7 +491,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|||||||
KeyCode::Tab,
|
KeyCode::Tab,
|
||||||
ReedlineEvent::UntilFound(vec![
|
ReedlineEvent::UntilFound(vec![
|
||||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||||
ReedlineEvent::MenuNext,
|
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
}
|
}
|
||||||
|
"space" => KeyCode::Char(' '),
|
||||||
"down" => KeyCode::Down,
|
"down" => KeyCode::Down,
|
||||||
"up" => KeyCode::Up,
|
"up" => KeyCode::Up,
|
||||||
"left" => KeyCode::Left,
|
"left" => KeyCode::Left,
|
||||||
@ -814,7 +815,6 @@ fn event_from_record(
|
|||||||
) -> Result<ReedlineEvent, ShellError> {
|
) -> Result<ReedlineEvent, ShellError> {
|
||||||
let event = match name {
|
let event = match name {
|
||||||
"none" => ReedlineEvent::None,
|
"none" => ReedlineEvent::None,
|
||||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
|
||||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||||
@ -822,6 +822,8 @@ fn event_from_record(
|
|||||||
"ctrld" => ReedlineEvent::CtrlD,
|
"ctrld" => ReedlineEvent::CtrlD,
|
||||||
"ctrlc" => ReedlineEvent::CtrlC,
|
"ctrlc" => ReedlineEvent::CtrlC,
|
||||||
"enter" => ReedlineEvent::Enter,
|
"enter" => ReedlineEvent::Enter,
|
||||||
|
"submit" => ReedlineEvent::Submit,
|
||||||
|
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||||
"esc" | "escape" => ReedlineEvent::Esc,
|
"esc" | "escape" => ReedlineEvent::Esc,
|
||||||
"up" => ReedlineEvent::Up,
|
"up" => ReedlineEvent::Up,
|
||||||
"down" => ReedlineEvent::Down,
|
"down" => ReedlineEvent::Down,
|
||||||
@ -875,7 +877,16 @@ fn edit_from_record(
|
|||||||
"moveleft" => EditCommand::MoveLeft,
|
"moveleft" => EditCommand::MoveLeft,
|
||||||
"moveright" => EditCommand::MoveRight,
|
"moveright" => EditCommand::MoveRight,
|
||||||
"movewordleft" => EditCommand::MoveWordLeft,
|
"movewordleft" => EditCommand::MoveWordLeft,
|
||||||
|
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
||||||
"movewordright" => EditCommand::MoveWordRight,
|
"movewordright" => EditCommand::MoveWordRight,
|
||||||
|
"movewordrightend" => EditCommand::MoveWordRightEnd,
|
||||||
|
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
|
||||||
|
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
||||||
|
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
||||||
|
"movetoposition" => {
|
||||||
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
|
EditCommand::MoveToPosition(value.as_integer()? as usize)
|
||||||
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
@ -888,6 +899,7 @@ fn edit_from_record(
|
|||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
"delete" => EditCommand::Delete,
|
"delete" => EditCommand::Delete,
|
||||||
|
"cutchar" => EditCommand::CutChar,
|
||||||
"backspaceword" => EditCommand::BackspaceWord,
|
"backspaceword" => EditCommand::BackspaceWord,
|
||||||
"deleteword" => EditCommand::DeleteWord,
|
"deleteword" => EditCommand::DeleteWord,
|
||||||
"clear" => EditCommand::Clear,
|
"clear" => EditCommand::Clear,
|
||||||
@ -898,7 +910,11 @@ fn edit_from_record(
|
|||||||
"cuttoend" => EditCommand::CutToEnd,
|
"cuttoend" => EditCommand::CutToEnd,
|
||||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||||
"cutwordleft" => EditCommand::CutWordLeft,
|
"cutwordleft" => EditCommand::CutWordLeft,
|
||||||
|
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||||
"cutwordright" => EditCommand::CutWordRight,
|
"cutwordright" => EditCommand::CutWordRight,
|
||||||
|
"cutbigwordright" => EditCommand::CutBigWordRight,
|
||||||
|
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
|
||||||
|
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
|
||||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||||
"uppercaseword" => EditCommand::UppercaseWord,
|
"uppercaseword" => EditCommand::UppercaseWord,
|
||||||
@ -948,6 +964,7 @@ fn edit_from_record(
|
|||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftBefore(char)
|
EditCommand::MoveLeftBefore(char)
|
||||||
}
|
}
|
||||||
|
"complete" => EditCommand::Complete,
|
||||||
e => {
|
e => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"reedline EditCommand".to_string(),
|
"reedline EditCommand".to_string(),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::get_shape_color;
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
|
use nu_protocol::ast::{Argument, Block, Expr, Expression};
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
use nu_protocol::Config;
|
use nu_protocol::{Config, Span};
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
|
|
||||||
pub struct NuHighlighter {
|
pub struct NuHighlighter {
|
||||||
@ -15,10 +16,12 @@ impl Highlighter for NuHighlighter {
|
|||||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||||
trace!("highlighting: {}", line);
|
trace!("highlighting: {}", line);
|
||||||
|
|
||||||
let (shapes, global_span_offset) = {
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let block = {
|
||||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
let (shapes, global_span_offset) = {
|
||||||
let shapes = flatten_block(&working_set, &block);
|
let shapes = flatten_block(&working_set, &block);
|
||||||
(shapes, self.engine_state.next_span_start())
|
(shapes, self.engine_state.next_span_start())
|
||||||
};
|
};
|
||||||
@ -26,6 +29,15 @@ impl Highlighter for NuHighlighter {
|
|||||||
let mut output = StyledText::default();
|
let mut output = StyledText::default();
|
||||||
let mut last_seen_span = global_span_offset;
|
let mut last_seen_span = global_span_offset;
|
||||||
|
|
||||||
|
let global_cursor_offset = _cursor + global_span_offset;
|
||||||
|
let matching_brackets_pos = find_matching_brackets(
|
||||||
|
line,
|
||||||
|
&working_set,
|
||||||
|
&block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
);
|
||||||
|
|
||||||
for shape in &shapes {
|
for shape in &shapes {
|
||||||
if shape.0.end <= last_seen_span
|
if shape.0.end <= last_seen_span
|
||||||
|| last_seen_span < global_span_offset
|
|| last_seen_span < global_span_offset
|
||||||
@ -44,166 +56,71 @@ impl Highlighter for NuHighlighter {
|
|||||||
let next_token = line
|
let next_token = line
|
||||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
macro_rules! add_colored_token_with_bracket_highlight {
|
||||||
|
($shape:expr, $span:expr, $text:expr) => {{
|
||||||
|
let spans = split_span_by_highlight_positions(
|
||||||
|
line,
|
||||||
|
&$span,
|
||||||
|
&matching_brackets_pos,
|
||||||
|
global_span_offset,
|
||||||
|
);
|
||||||
|
spans.iter().for_each(|(part, highlight)| {
|
||||||
|
let start = part.start - $span.start;
|
||||||
|
let end = part.end - $span.start;
|
||||||
|
let text = (&next_token[start..end]).to_string();
|
||||||
|
let mut style = get_shape_color($shape.to_string(), &self.config);
|
||||||
|
if *highlight {
|
||||||
|
style = get_matching_brackets_style(style, &self.config);
|
||||||
|
}
|
||||||
|
output.push((style, text));
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_colored_token {
|
||||||
|
($shape:expr, $text:expr) => {
|
||||||
|
output.push((get_shape_color($shape.to_string(), &self.config), $text))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match shape.1 {
|
match shape.1 {
|
||||||
FlatShape::Garbage => output.push((
|
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Garbage
|
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Binary => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::Bool => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Int => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Nothing => output.push((
|
FlatShape::Float => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Nothing
|
FlatShape::Range => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::External => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Binary => {
|
FlatShape::Literal => add_colored_token!(shape.1, next_token),
|
||||||
// nushell ?
|
FlatShape::Operator => add_colored_token!(shape.1, next_token),
|
||||||
output.push((
|
FlatShape::Signature => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::String => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
|
||||||
))
|
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
|
||||||
}
|
|
||||||
FlatShape::Bool => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Int => {
|
|
||||||
// nushell Int
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Float => {
|
|
||||||
// nushell Decimal
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Range => output.push((
|
|
||||||
// nushell DotDot ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::InternalCall => output.push((
|
|
||||||
// nushell InternalCommand
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::External => {
|
|
||||||
// nushell ExternalCommand
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::ExternalArg => {
|
|
||||||
// nushell ExternalWord
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Literal => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Operator => output.push((
|
|
||||||
// nushell Operator
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Signature => output.push((
|
|
||||||
// nushell ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::String => {
|
|
||||||
// nushell String
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::StringInterpolation => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::DateTime => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::List => {
|
FlatShape::List => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Table => {
|
FlatShape::Table => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Record => {
|
FlatShape::Record => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatShape::Block => {
|
FlatShape::Block => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Filepath => output.push((
|
|
||||||
// nushell Path
|
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Directory => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Directory => output.push((
|
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Directory
|
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::GlobPattern => output.push((
|
|
||||||
// nushell GlobPattern
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Variable => output.push((
|
|
||||||
// nushell Variable
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Flag => {
|
|
||||||
// nushell Flag
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Custom(..) => output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
last_seen_span = shape.0.end;
|
last_seen_span = shape.0.end;
|
||||||
}
|
}
|
||||||
@ -216,3 +133,297 @@ impl Highlighter for NuHighlighter {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_span_by_highlight_positions(
|
||||||
|
line: &str,
|
||||||
|
span: &Span,
|
||||||
|
highlight_positions: &Vec<usize>,
|
||||||
|
global_span_offset: usize,
|
||||||
|
) -> Vec<(Span, bool)> {
|
||||||
|
let mut start = span.start;
|
||||||
|
let mut result: Vec<(Span, bool)> = Vec::new();
|
||||||
|
for pos in highlight_positions {
|
||||||
|
if start <= *pos && pos < &span.end {
|
||||||
|
if start < *pos {
|
||||||
|
result.push((Span { start, end: *pos }, false));
|
||||||
|
}
|
||||||
|
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
||||||
|
let end = span_str
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.map(|c| pos + get_char_length(c))
|
||||||
|
.unwrap_or(pos + 1);
|
||||||
|
result.push((Span { start: *pos, end }, true));
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < span.end {
|
||||||
|
result.push((
|
||||||
|
Span {
|
||||||
|
start,
|
||||||
|
end: span.end,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_brackets(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Vec<usize> {
|
||||||
|
const BRACKETS: &str = "{}[]()";
|
||||||
|
|
||||||
|
// calculate first bracket position
|
||||||
|
let global_end_offset = line.len() + global_span_offset;
|
||||||
|
let global_bracket_pos =
|
||||||
|
if global_cursor_offset == global_end_offset && global_end_offset > global_span_offset {
|
||||||
|
// cursor is at the end of a non-empty string -- find block end at the previous position
|
||||||
|
if let Some(last_char) = line.chars().last() {
|
||||||
|
global_cursor_offset - get_char_length(last_char)
|
||||||
|
} else {
|
||||||
|
global_cursor_offset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// cursor is in the middle of a string -- find block end at the current position
|
||||||
|
global_cursor_offset
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that position contains bracket
|
||||||
|
let match_idx = global_bracket_pos - global_span_offset;
|
||||||
|
if match_idx >= line.len()
|
||||||
|
|| !BRACKETS.contains(get_char_at_index(line, match_idx).unwrap_or_default())
|
||||||
|
{
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// find matching bracket by finding matching block end
|
||||||
|
let matching_block_end = find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
block,
|
||||||
|
global_span_offset,
|
||||||
|
global_bracket_pos,
|
||||||
|
);
|
||||||
|
if let Some(pos) = matching_block_end {
|
||||||
|
let matching_idx = pos - global_span_offset;
|
||||||
|
if BRACKETS.contains(get_char_at_index(line, matching_idx).unwrap_or_default()) {
|
||||||
|
return if global_bracket_pos < pos {
|
||||||
|
vec![global_bracket_pos, pos]
|
||||||
|
} else {
|
||||||
|
vec![pos, global_bracket_pos]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_block(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
for p in &block.pipelines {
|
||||||
|
for e in &p.expressions {
|
||||||
|
if e.span.contains(global_cursor_offset) {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
e,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_expr(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expression: &Expression,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
macro_rules! find_in_expr_or_continue {
|
||||||
|
($inner_expr:ident) => {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
$inner_expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
|
||||||
|
{
|
||||||
|
let expr_first = expression.span.start;
|
||||||
|
let span_str = &line
|
||||||
|
[expression.span.start - global_span_offset..expression.span.end - global_span_offset];
|
||||||
|
let expr_last = span_str
|
||||||
|
.chars()
|
||||||
|
.last()
|
||||||
|
.map(|c| expression.span.end - get_char_length(c))
|
||||||
|
.unwrap_or(expression.span.start);
|
||||||
|
|
||||||
|
return match &expression.expr {
|
||||||
|
Expr::Bool(_) => None,
|
||||||
|
Expr::Int(_) => None,
|
||||||
|
Expr::Float(_) => None,
|
||||||
|
Expr::Binary(_) => None,
|
||||||
|
Expr::Range(..) => None,
|
||||||
|
Expr::Var(_) => None,
|
||||||
|
Expr::VarDecl(_) => None,
|
||||||
|
Expr::ExternalCall(..) => None,
|
||||||
|
Expr::Operator(_) => None,
|
||||||
|
Expr::UnaryNot(_) => None,
|
||||||
|
Expr::Keyword(..) => None,
|
||||||
|
Expr::ValueWithUnit(..) => None,
|
||||||
|
Expr::DateTime(_) => None,
|
||||||
|
Expr::Filepath(_) => None,
|
||||||
|
Expr::Directory(_) => None,
|
||||||
|
Expr::GlobPattern(_) => None,
|
||||||
|
Expr::String(_) => None,
|
||||||
|
Expr::CellPath(_) => None,
|
||||||
|
Expr::ImportPattern(_) => None,
|
||||||
|
Expr::Overlay(_) => None,
|
||||||
|
Expr::Signature(_) => None,
|
||||||
|
Expr::Nothing => None,
|
||||||
|
Expr::Garbage => None,
|
||||||
|
|
||||||
|
Expr::Table(hdr, rows) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at table end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at table start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside table
|
||||||
|
for inner_expr in hdr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
for row in rows {
|
||||||
|
for inner_expr in row {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Record(exprs) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at record end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at record start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside record
|
||||||
|
for (k, v) in exprs {
|
||||||
|
find_in_expr_or_continue!(k);
|
||||||
|
find_in_expr_or_continue!(v);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Call(call) => {
|
||||||
|
for arg in &call.arguments {
|
||||||
|
let opt_expr = match arg {
|
||||||
|
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||||
|
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(inner_expr) = opt_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
&b.head,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
),
|
||||||
|
|
||||||
|
Expr::BinaryOp(lhs, op, rhs) => {
|
||||||
|
find_in_expr_or_continue!(lhs);
|
||||||
|
find_in_expr_or_continue!(op);
|
||||||
|
find_in_expr_or_continue!(rhs);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Block(block_id)
|
||||||
|
| Expr::RowCondition(block_id)
|
||||||
|
| Expr::Subexpression(block_id) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at block end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at block start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside block
|
||||||
|
let nested_block = working_set.get_block(*block_id);
|
||||||
|
find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
nested_block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::StringInterpolation(inner_expr) => {
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::List(inner_expr) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at list end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at list start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside list
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_at_index(s: &str, index: usize) -> Option<char> {
|
||||||
|
s[index..].chars().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_length(c: char) -> usize {
|
||||||
|
c.to_string().len()
|
||||||
|
}
|
||||||
|
@ -1,26 +1,43 @@
|
|||||||
use log::trace;
|
use crate::repl::eval_hook;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::CliError;
|
use nu_protocol::CliError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, ShellError, Span, Value,
|
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
//
|
//
|
||||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||||
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
||||||
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||||
pub fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
//
|
||||||
gather_env_vars(std::env::vars(), engine_state);
|
// The "PWD" env value will be forced to `init_cwd`.
|
||||||
|
// The reason to use `init_cwd`:
|
||||||
|
//
|
||||||
|
// While gathering parent env vars, the parent `PWD` may not be the same as `current working directory`.
|
||||||
|
// Consider to the following command as the case (assume we execute command inside `/tmp`):
|
||||||
|
//
|
||||||
|
// tmux split-window -v -c "#{pane_current_path}"
|
||||||
|
//
|
||||||
|
// Here nu execute external command `tmux`, and tmux starts a new `nushell`, with `init_cwd` value "#{pane_current_path}".
|
||||||
|
// But at the same time `PWD` still remains to be `/tmp`.
|
||||||
|
//
|
||||||
|
// In this scenario, the new `nushell`'s PWD should be "#{pane_current_path}" rather init_cwd.
|
||||||
|
pub fn gather_parent_env_vars(engine_state: &mut EngineState, init_cwd: &Path) {
|
||||||
|
gather_env_vars(std::env::vars(), engine_state, init_cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &mut EngineState) {
|
fn gather_env_vars(
|
||||||
|
vars: impl Iterator<Item = (String, String)>,
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
init_cwd: &Path,
|
||||||
|
) {
|
||||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
@ -43,35 +60,31 @@ fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut fake_env_file = String::new();
|
let mut fake_env_file = String::new();
|
||||||
let mut has_pwd = false;
|
|
||||||
|
|
||||||
// Write all the env vars into a fake file
|
// Write all the env vars into a fake file
|
||||||
for (name, val) in vars {
|
for (name, val) in vars {
|
||||||
if name == "PWD" {
|
|
||||||
has_pwd = true;
|
|
||||||
}
|
|
||||||
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_pwd {
|
match init_cwd.to_str() {
|
||||||
match std::env::current_dir() {
|
Some(cwd) => {
|
||||||
Ok(cwd) => {
|
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
|
||||||
put_env_to_fake_file("PWD", &cwd.to_string_lossy(), &mut fake_env_file);
|
}
|
||||||
}
|
None => {
|
||||||
Err(e) => {
|
// Could not capture current working directory
|
||||||
// Could not capture current working directory
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
report_error(
|
||||||
report_error(
|
&working_set,
|
||||||
&working_set,
|
&ShellError::GenericError(
|
||||||
&ShellError::GenericError(
|
"Current directory is not a valid utf-8 path".to_string(),
|
||||||
"Current directory not found".to_string(),
|
"".to_string(),
|
||||||
"".to_string(),
|
None,
|
||||||
None,
|
Some(format!(
|
||||||
Some(format!("Retrieving current directory failed: {:?}", e)),
|
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||||
Vec::new(),
|
init_cwd
|
||||||
),
|
)),
|
||||||
);
|
Vec::new(),
|
||||||
}
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,8 +204,6 @@ pub fn eval_source(
|
|||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
trace!("eval_source");
|
|
||||||
|
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let (output, err) = parse(
|
let (output, err) = parse(
|
||||||
@ -211,35 +222,48 @@ pub fn eval_source(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
Ok(p) => p,
|
set_last_exit_code(stack, 1);
|
||||||
Err(e) => {
|
report_error_new(engine_state, &err);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
return false;
|
||||||
report_error(&working_set, &e);
|
}
|
||||||
get_init_cwd()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(mut pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
let config = engine_state.get_config();
|
||||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
let result;
|
||||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
if let PipelineData::ExternalStream {
|
||||||
} else {
|
stdout: stream,
|
||||||
set_last_exit_code(stack, 0);
|
stderr: stderr_stream,
|
||||||
|
exit_code,
|
||||||
|
..
|
||||||
|
} = pipeline_data
|
||||||
|
{
|
||||||
|
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
||||||
|
} else if let Some(hook) = config.hooks.display_output.clone() {
|
||||||
|
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
|
||||||
|
Err(err) => {
|
||||||
|
result = Err(err);
|
||||||
|
}
|
||||||
|
Ok(val) => {
|
||||||
|
result = val.print(engine_state, stack, false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
set_last_exit_code(stack, 0);
|
result = pipeline_data.print(engine_state, stack, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = pipeline_data.print(engine_state, stack, false) {
|
match result {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
Err(err) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
Ok(exit_code) => {
|
||||||
|
set_last_exit_code(stack, exit_code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
@ -284,6 +308,15 @@ pub fn report_error(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_error_new(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||||
|
) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, error);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_init_cwd() -> PathBuf {
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
match std::env::current_dir() {
|
match std::env::current_dir() {
|
||||||
Ok(cwd) => cwd,
|
Ok(cwd) => cwd,
|
||||||
@ -297,6 +330,17 @@ pub fn get_init_cwd() -> PathBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||||
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
get_init_cwd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -314,6 +358,7 @@ mod test {
|
|||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
|
Path::new("t"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let env = engine_state.render_env_vars();
|
let env = engine_state.render_env_vars();
|
||||||
|
734
crates/nu-cli/tests/completions.rs
Normal file
734
crates/nu-cli/tests/completions.rs
Normal file
@ -0,0 +1,734 @@
|
|||||||
|
pub mod support;
|
||||||
|
|
||||||
|
use nu_cli::NuCompleter;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
|
use reedline::{Completer, Suggestion};
|
||||||
|
use rstest::{fixture, rstest};
|
||||||
|
use support::{file, folder, match_suggestions, new_engine};
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = "def tst [--mod -s] {}";
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn completer_strings() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||||
|
def my-command [animal: string@animals] { print $animal }"#;
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variables_dollar_sign_with_varialblecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "$ ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
assert_eq!(7, suggestions.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst --", 6);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||||
|
// dbg!(&expected, &suggestions);
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-c ", 4);
|
||||||
|
let expected: Vec<String> = vec!["my-command".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||||
|
mut completer_strings: NuCompleter,
|
||||||
|
) {
|
||||||
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dotnu_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test source completion
|
||||||
|
let completion_str = "source-env ".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||||
|
|
||||||
|
// Test use completion
|
||||||
|
let completion_str = "use ".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn external_completer_trailing_space() {
|
||||||
|
// https://github.com/nushell/nushell/issues/6378
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh alias ".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(3, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
|
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_completer_no_trailing_space() {
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh alias".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_completer_pass_flags() {
|
||||||
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
|
let input = "gh api --".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert_eq!(3, suggestions.len());
|
||||||
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
|
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||||
|
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for the current folder
|
||||||
|
let target_dir = format!("cp {}", dir_str);
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
file(dir.join("nushell")),
|
||||||
|
folder(dir.join("test_a")),
|
||||||
|
folder(dir.join("test_b")),
|
||||||
|
folder(dir.join("another")),
|
||||||
|
file(dir.join("custom_completion.nu")),
|
||||||
|
file(dir.join(".hidden_file")),
|
||||||
|
folder(dir.join(".hidden_folder")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completions for a file
|
||||||
|
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_ls_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "ls ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn command_open_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "open ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_rm_with_globcompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "rm ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_cp_with_globcompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "cp ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_save_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "save ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_touch_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "touch ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_watch_with_filecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "watch ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flag_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
// Test completions for the 'ls' flags
|
||||||
|
let suggestions = completer.complete("ls -", 4);
|
||||||
|
|
||||||
|
assert_eq!(14, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec![
|
||||||
|
"--all".into(),
|
||||||
|
"--directory".into(),
|
||||||
|
"--du".into(),
|
||||||
|
"--full-paths".into(),
|
||||||
|
"--help".into(),
|
||||||
|
"--long".into(),
|
||||||
|
"--short-names".into(),
|
||||||
|
"-D".into(),
|
||||||
|
"-a".into(),
|
||||||
|
"-d".into(),
|
||||||
|
"-f".into(),
|
||||||
|
"-h".into(),
|
||||||
|
"-l".into(),
|
||||||
|
"-s".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn folder_with_directorycompletions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for the current folder
|
||||||
|
let target_dir = format!("cd {}", dir_str);
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
folder(dir.join("test_a")),
|
||||||
|
folder(dir.join("test_b")),
|
||||||
|
folder(dir.join("another")),
|
||||||
|
folder(dir.join(".hidden_folder")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variables_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for $nu
|
||||||
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
|
assert_eq!(9, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec![
|
||||||
|
"config-path".into(),
|
||||||
|
"env-path".into(),
|
||||||
|
"history-path".into(),
|
||||||
|
"home-path".into(),
|
||||||
|
"loginshell-path".into(),
|
||||||
|
"os-info".into(),
|
||||||
|
"pid".into(),
|
||||||
|
"scope".into(),
|
||||||
|
"temp-path".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $nu.h (filter)
|
||||||
|
let suggestions = completer.complete("$nu.h", 5);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for custom var
|
||||||
|
let suggestions = completer.complete("$actor.", 7);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for custom var (filtering)
|
||||||
|
let suggestions = completer.complete("$actor.n", 8);
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["name".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $env
|
||||||
|
let suggestions = completer.complete("$env.", 5);
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
|
||||||
|
// Test completions for $env
|
||||||
|
let suggestions = completer.complete("$env.T", 6);
|
||||||
|
|
||||||
|
assert_eq!(1, suggestions.len());
|
||||||
|
|
||||||
|
let expected: Vec<String> = vec!["TEST".into()];
|
||||||
|
|
||||||
|
// Match results
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_command_and_flags() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls -l"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("ll t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_basic_command() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls "#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("ll t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_of_another_alias() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls -la"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||||
|
// Create the second alias
|
||||||
|
let alias = r#"alias lf = ll -f"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("lf t", 4);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||||
|
let (_, delta) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
|
||||||
|
assert!(err.is_none());
|
||||||
|
|
||||||
|
(block, working_set.render())
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(engine_state.merge_delta(delta).is_ok());
|
||||||
|
|
||||||
|
// Merge environment into the permanent state
|
||||||
|
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||||
|
|
||||||
|
let latest_block_id = engine_state.num_blocks() - 1;
|
||||||
|
|
||||||
|
// Change config adding the external completer
|
||||||
|
let mut config = engine_state.get_config().clone();
|
||||||
|
config.external_completer = Some(latest_block_id);
|
||||||
|
engine_state.set_config(&config);
|
||||||
|
|
||||||
|
// Instatiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||||
|
|
||||||
|
completer.complete(input, input.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_command_completion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "thiscommanddoesnotexist ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h | ls", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filecompletions_triggers_after_cursor() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("cp test_c", 3);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variables_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, _, mut engine, mut stack) = new_engine();
|
|
||||||
|
|
||||||
// Add record value as example
|
|
||||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
|
||||||
def my-command [animal: string@animals] { print $animal }"#;
|
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for $nu
|
|
||||||
let suggestions = completer.complete("my-command ".into(), 11);
|
|
||||||
|
|
||||||
assert_eq!(3, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::new_engine;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dotnu_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test source completion
|
|
||||||
let completion_str = "source ".to_string();
|
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
|
||||||
|
|
||||||
// Test use completion
|
|
||||||
let completion_str = "use ".to_string();
|
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{file, folder, match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn file_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for the current folder
|
|
||||||
let target_dir = format!("cp {}", dir_str);
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![
|
|
||||||
file(dir.join("nushell")),
|
|
||||||
folder(dir.join("test_a")),
|
|
||||||
folder(dir.join("test_b")),
|
|
||||||
folder(dir.join("another")),
|
|
||||||
file(dir.join("custom_completion.nu")),
|
|
||||||
file(dir.join(".hidden_file")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
|
|
||||||
// Test completions for the completions/another folder
|
|
||||||
let target_dir = format!("cd {}", folder(dir.join("another")));
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn flag_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
// Test completions for the 'ls' flags
|
|
||||||
let suggestions = completer.complete("ls -".into(), 4);
|
|
||||||
|
|
||||||
assert_eq!(12, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"--all".into(),
|
|
||||||
"--du".into(),
|
|
||||||
"--full-paths".into(),
|
|
||||||
"--help".into(),
|
|
||||||
"--long".into(),
|
|
||||||
"--short-names".into(),
|
|
||||||
"-a".into(),
|
|
||||||
"-d".into(),
|
|
||||||
"-f".into(),
|
|
||||||
"-h".into(),
|
|
||||||
"-l".into(),
|
|
||||||
"-s".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{folder, match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn folder_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for the current folder
|
|
||||||
let target_dir = format!("cd {}", dir_str);
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![
|
|
||||||
folder(dir.join("test_a")),
|
|
||||||
folder(dir.join("test_b")),
|
|
||||||
folder(dir.join("another")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ use nu_command::create_default_context;
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, ShellError, Span, Value,
|
PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs;
|
use nu_test_support::fs;
|
||||||
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
dir_str.push(SEP);
|
dir_str.push(SEP);
|
||||||
|
|
||||||
// Create a new engine with default context
|
// Create a new engine with default context
|
||||||
let mut engine_state = create_default_context(&dir);
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
// New stack
|
// New stack
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
// New delta state
|
|
||||||
let delta = StateDelta::new(&engine_state);
|
|
||||||
|
|
||||||
// Add pwd as env var
|
// Add pwd as env var
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
|
|
||||||
// match a list of suggestions with the expected values
|
// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
|
let expected_len = expected.len();
|
||||||
|
let suggestions_len = suggestions.len();
|
||||||
|
if expected_len != suggestions_len {
|
||||||
|
panic!(
|
||||||
|
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||||
|
Suggestions: {suggestions:#?} \n\
|
||||||
|
Expected: {expected:#?}\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
expected.iter().zip(suggestions).for_each(|it| {
|
expected.iter().zip(suggestions).for_each(|it| {
|
||||||
assert_eq!(it.0, &it.1.value);
|
assert_eq!(it.0, &it.1.value);
|
||||||
});
|
});
|
||||||
@ -89,7 +95,7 @@ pub fn merge_input(
|
|||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
||||||
|
|
||||||
@ -97,8 +103,11 @@ pub fn merge_input(
|
|||||||
|
|
||||||
(block, working_set.render())
|
(block, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
assert!(eval_block(
|
assert!(eval_block(
|
||||||
&engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&block,
|
&block,
|
||||||
PipelineData::Value(
|
PipelineData::Value(
|
||||||
@ -112,6 +121,6 @@ pub fn merge_input(
|
|||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
engine_state.merge_env(stack, &dir)
|
||||||
}
|
}
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variables_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, _, mut engine, mut stack) = new_engine();
|
|
||||||
|
|
||||||
// Add record value as example
|
|
||||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for $nu
|
|
||||||
let suggestions = completer.complete("$nu.".into(), 4);
|
|
||||||
|
|
||||||
assert_eq!(8, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"config-path".into(),
|
|
||||||
"env-path".into(),
|
|
||||||
"history-path".into(),
|
|
||||||
"home-path".into(),
|
|
||||||
"os-info".into(),
|
|
||||||
"pid".into(),
|
|
||||||
"scope".into(),
|
|
||||||
"temp-path".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for $nu.h (filter)
|
|
||||||
let suggestions = completer.complete("$nu.h".into(), 5);
|
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for custom var
|
|
||||||
let suggestions = completer.complete("$actor.".into(), 7);
|
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for custom var (filtering)
|
|
||||||
let suggestions = completer.complete("$actor.n".into(), 7);
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["name".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for $env
|
|
||||||
let suggestions = completer.complete("$env.".into(), 5);
|
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
|
|
||||||
// Test completions for $env
|
|
||||||
let suggestions = completer.complete("$env.T".into(), 5);
|
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["TEST".into()];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,14 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Color configuration code used by Nushell"
|
description = "Color configuration code used by Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.63.1"
|
version = "0.71.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-ansi-term = "0.45.1"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-json = { path = "../nu-json", version = "0.63.1" }
|
nu-json = { path = "../nu-json", version = "0.71.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.63.1" }
|
nu-table = { path = "../nu-table", version = "0.71.0" }
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
mod color_config;
|
mod color_config;
|
||||||
|
mod matching_brackets_style;
|
||||||
mod nu_style;
|
mod nu_style;
|
||||||
mod shape_color;
|
mod shape_color;
|
||||||
|
|
||||||
pub use color_config::*;
|
pub use color_config::*;
|
||||||
|
pub use matching_brackets_style::*;
|
||||||
pub use nu_style::*;
|
pub use nu_style::*;
|
||||||
pub use shape_color::*;
|
pub use shape_color::*;
|
||||||
|
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::color_config::lookup_ansi_color_style;
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_protocol::Config;
|
||||||
|
|
||||||
|
pub fn get_matching_brackets_style(default_style: Style, conf: &Config) -> Style {
|
||||||
|
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
|
||||||
|
|
||||||
|
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
|
||||||
|
Some(int_color) => match int_color.as_string() {
|
||||||
|
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
|
||||||
|
Err(_) => default_style,
|
||||||
|
},
|
||||||
|
None => default_style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_styles(base: Style, extra: Style) -> Style {
|
||||||
|
Style {
|
||||||
|
foreground: extra.foreground.or(base.foreground),
|
||||||
|
background: extra.background.or(base.background),
|
||||||
|
is_bold: extra.is_bold || base.is_bold,
|
||||||
|
is_dimmed: extra.is_dimmed || base.is_dimmed,
|
||||||
|
is_italic: extra.is_italic || base.is_italic,
|
||||||
|
is_underline: extra.is_underline || base.is_underline,
|
||||||
|
is_blink: extra.is_blink || base.is_blink,
|
||||||
|
is_reverse: extra.is_reverse || base.is_reverse,
|
||||||
|
is_hidden: extra.is_hidden || base.is_hidden,
|
||||||
|
is_strikethrough: extra.is_strikethrough || base.is_strikethrough,
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Debug)]
|
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||||
pub struct NuStyle {
|
pub struct NuStyle {
|
||||||
pub fg: Option<String>,
|
pub fg: Option<String>,
|
||||||
pub bg: Option<String>,
|
pub bg: Option<String>,
|
||||||
@ -11,12 +11,12 @@ pub struct NuStyle {
|
|||||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||||
// get the nu_ansi_term::Color foreground color
|
// get the nu_ansi_term::Color foreground color
|
||||||
let fg_color = match nu_style.fg {
|
let fg_color = match nu_style.fg {
|
||||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// get the nu_ansi_term::Color background color
|
// get the nu_ansi_term::Color background color
|
||||||
let bg_color = match nu_style.bg {
|
let bg_color = match nu_style.bg {
|
||||||
Some(bg) => color_from_hex(&bg).expect("error with background color"),
|
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// get the attributes
|
// get the attributes
|
||||||
|
@ -1,45 +1,48 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Nushell's built-in commands"
|
description = "Nushell's built-in commands"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.63.1"
|
version = "0.71.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.63.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.63.1" }
|
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.63.1" }
|
nu-glob = { path = "../nu-glob", version = "0.71.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.63.1" }
|
nu-json = { path = "../nu-json", version = "0.71.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.63.1" }
|
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.63.1" }
|
nu-path = { path = "../nu-path", version = "0.71.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.63.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.63.1" }
|
nu-system = { path = "../nu-system", version = "0.71.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.63.1" }
|
nu-table = { path = "../nu-table", version = "0.71.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.63.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.63.1" }
|
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.63.1" }
|
nu-ansi-term = "0.46.0"
|
||||||
nu-ansi-term = "0.45.1"
|
num-format = { version = "0.4.3" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
byteorder = "1.4.3"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
calamine = "0.18.0"
|
calamine = "0.18.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
chrono-tz = "0.6.1"
|
chrono-tz = "0.6.3"
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
dialoguer = "0.9.0"
|
dialoguer = "0.9.0"
|
||||||
digest = "0.10.0"
|
digest = "0.10.0"
|
||||||
dtparse = "1.2.0"
|
dtparse = "1.2.0"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
encoding_rs = "0.8.30"
|
encoding_rs = "0.8.30"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
filetime = "0.2.15"
|
filetime = "0.2.15"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
@ -47,64 +50,96 @@ htmlescape = "0.3.1"
|
|||||||
ical = "0.7.0"
|
ical = "0.7.0"
|
||||||
indexmap = { version="1.7", features=["serde-1"] }
|
indexmap = { version="1.7", features=["serde-1"] }
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
|
is-root = "0.1.2"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
meval = "0.2.0"
|
meval = "0.2.0"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
num = { version = "0.4.0", optional = true }
|
num = { version = "0.4.0", optional = true }
|
||||||
|
num-traits = "0.2.14"
|
||||||
pathdiff = "0.2.1"
|
pathdiff = "0.2.1"
|
||||||
powierza-coefficient = "1.0"
|
powierza-coefficient = "1.0.1"
|
||||||
quick-xml = "0.22"
|
quick-xml = "0.23.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
regex = "1.5.4"
|
|
||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||||
roxmltree = "0.14.0"
|
roxmltree = "0.14.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
same-file = "1.0.6"
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8.16"
|
serde_yaml = "0.9.4"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
strip-ansi-escapes = "0.1.1"
|
sysinfo = "0.26.2"
|
||||||
sysinfo = "0.23.5"
|
terminal_size = "0.2.1"
|
||||||
terminal_size = "0.1.17"
|
thiserror = "1.0.31"
|
||||||
thiserror = "1.0.29"
|
titlecase = "2.0.0"
|
||||||
titlecase = "1.1.0"
|
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
unicode-segmentation = "1.8.0"
|
unicode-segmentation = "1.8.0"
|
||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
which = { version = "4.2.2", optional = true }
|
which = { version = "4.3.0", optional = true }
|
||||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
wax = { version = "0.4.0", features = ["diagnostics"] }
|
wax = { version = "0.5.0" }
|
||||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "2.0.0"
|
umask = "2.0.0"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
version = "0.21.1"
|
version = "0.23.2"
|
||||||
# path = "../../../../polars/polars"
|
|
||||||
optional = true
|
optional = true
|
||||||
features = [
|
features = [
|
||||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
"arg_where",
|
||||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
"checked_arithmetic",
|
||||||
"rolling_window", "strings", "rows", "random",
|
"concat_str",
|
||||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
"cross_join",
|
||||||
"dynamic_groupby"
|
"csv-file",
|
||||||
|
"cum_agg",
|
||||||
|
"default",
|
||||||
|
"dtype-datetime",
|
||||||
|
"dtype-struct",
|
||||||
|
"dtype-categorical",
|
||||||
|
"dynamic_groupby",
|
||||||
|
"ipc",
|
||||||
|
"is_in",
|
||||||
|
"json",
|
||||||
|
"lazy",
|
||||||
|
"object",
|
||||||
|
"parquet",
|
||||||
|
"random",
|
||||||
|
"rolling_window",
|
||||||
|
"rows",
|
||||||
|
"serde",
|
||||||
|
"serde-lazy",
|
||||||
|
"strings",
|
||||||
|
"strings",
|
||||||
|
"to_dummies",
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
|
version = "0.42.0"
|
||||||
|
features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -115,10 +150,14 @@ dataframe = ["polars", "num"]
|
|||||||
database = ["sqlparser", "rusqlite"]
|
database = ["sqlparser", "rusqlite"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
|
||||||
|
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
|
proptest = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
@ -3,16 +3,18 @@ use std::process::Command;
|
|||||||
fn main() -> shadow_rs::SdResult<()> {
|
fn main() -> shadow_rs::SdResult<()> {
|
||||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
let hash = get_git_hash().unwrap_or_default();
|
||||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||||
|
|
||||||
shadow_rs::new()
|
shadow_rs::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
fn get_git_hash() -> Option<String> {
|
||||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
Command::new("git")
|
||||||
Ok(String::from_utf8(out.stdout)
|
.args(["rev-parse", "HEAD"])
|
||||||
.expect("could not convert stdout to string")
|
.output()
|
||||||
.trim()
|
.ok()
|
||||||
.to_string())
|
.filter(|output| output.status.success())
|
||||||
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||||
|
.map(|hash| hash.trim().to_string())
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
|
||||||
|
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
|
||||||
|
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
|
||||||
|
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
|
||||||
|
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
|
||||||
|
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
|
||||||
|
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
|
||||||
|
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
|
||||||
|
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
|
||||||
|
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
|
||||||
|
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
|
||||||
|
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
|
||||||
|
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
|
||||||
|
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
|
||||||
|
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
|
||||||
|
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
|
||||||
|
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"
|
100
crates/nu-command/src/bits/and.rs
Normal file
100
crates/nu-command/src/bits/and.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits and"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits and")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit and",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise and for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic and"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits and to two numbers",
|
||||||
|
example: "2 | bits and 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 2,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical and to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits and 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val & target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/bits/bits_.rs
Normal file
49
crates/nu-command/src/bits/bits_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_engine::get_full_help;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Bits;
|
||||||
|
|
||||||
|
impl Command for Bits {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits").category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Various commands for working with bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: get_full_help(&Bits.signature(), &Bits.examples(), engine_state, stack),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Bits;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Bits {})
|
||||||
|
}
|
||||||
|
}
|
99
crates/nu-command/src/bits/mod.rs
Normal file
99
crates/nu-command/src/bits/mod.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
mod and;
|
||||||
|
mod bits_;
|
||||||
|
mod not;
|
||||||
|
mod or;
|
||||||
|
mod rotate_left;
|
||||||
|
mod rotate_right;
|
||||||
|
mod shift_left;
|
||||||
|
mod shift_right;
|
||||||
|
mod xor;
|
||||||
|
|
||||||
|
use nu_protocol::Spanned;
|
||||||
|
|
||||||
|
pub use and::SubCommand as BitsAnd;
|
||||||
|
pub use bits_::Bits;
|
||||||
|
pub use not::SubCommand as BitsNot;
|
||||||
|
pub use or::SubCommand as BitsOr;
|
||||||
|
pub use rotate_left::SubCommand as BitsRotateLeft;
|
||||||
|
pub use rotate_right::SubCommand as BitsRotateRight;
|
||||||
|
pub use shift_left::SubCommand as BitsShiftLeft;
|
||||||
|
pub use shift_right::SubCommand as BitsShiftRight;
|
||||||
|
pub use xor::SubCommand as BitsXor;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum NumberBytes {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
Auto,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum InputNumType {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
SignedOne,
|
||||||
|
SignedTwo,
|
||||||
|
SignedFour,
|
||||||
|
SignedEight,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_number_bytes(number_bytes: &Option<Spanned<String>>) -> NumberBytes {
|
||||||
|
match number_bytes.as_ref() {
|
||||||
|
None => NumberBytes::Eight,
|
||||||
|
Some(size) => match size.item.as_str() {
|
||||||
|
"1" => NumberBytes::One,
|
||||||
|
"2" => NumberBytes::Two,
|
||||||
|
"4" => NumberBytes::Four,
|
||||||
|
"8" => NumberBytes::Eight,
|
||||||
|
"auto" => NumberBytes::Auto,
|
||||||
|
_ => NumberBytes::Invalid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> InputNumType {
|
||||||
|
if signed || val < 0 {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::SignedOne,
|
||||||
|
NumberBytes::Two => InputNumType::SignedTwo,
|
||||||
|
NumberBytes::Four => InputNumType::SignedFour,
|
||||||
|
NumberBytes::Eight => InputNumType::SignedEight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0x7F && val >= -(2i64.pow(7)) {
|
||||||
|
InputNumType::SignedOne
|
||||||
|
} else if val <= 0x7FFF && val >= -(2i64.pow(15)) {
|
||||||
|
InputNumType::SignedTwo
|
||||||
|
} else if val <= 0x7FFFFFFF && val >= -(2i64.pow(31)) {
|
||||||
|
InputNumType::SignedFour
|
||||||
|
} else {
|
||||||
|
InputNumType::SignedEight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::SignedFour,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::One,
|
||||||
|
NumberBytes::Two => InputNumType::Two,
|
||||||
|
NumberBytes::Four => InputNumType::Four,
|
||||||
|
NumberBytes::Eight => InputNumType::Eight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
InputNumType::One
|
||||||
|
} else if val <= 0xFFFF {
|
||||||
|
InputNumType::Two
|
||||||
|
} else if val <= 0xFFFFFFFF {
|
||||||
|
InputNumType::Four
|
||||||
|
} else {
|
||||||
|
InputNumType::Eight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::Four,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
crates/nu-command/src/bits/not.rs
Normal file
163
crates/nu-command/src/bits/not.rs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
use super::{get_number_bytes, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits not"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits not")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs logical negation on each bit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["negation"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply logical negation to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits not",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(140737488355323),
|
||||||
|
Value::test_int(140737488355324),
|
||||||
|
Value::test_int(140737488355325),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||||
|
example: "[4 3 2] | bits not -n 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(65531),
|
||||||
|
Value::test_int(65532),
|
||||||
|
Value::test_int(65533),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as signed number",
|
||||||
|
example: "[4 3 2] | bits not -s",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(-5),
|
||||||
|
Value::test_int(-4),
|
||||||
|
Value::test_int(-3),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
if signed || val < 0 {
|
||||||
|
Value::Int { val: !val, span }
|
||||||
|
} else {
|
||||||
|
use NumberBytes::*;
|
||||||
|
let out_val = match number_size {
|
||||||
|
One => !val & 0x00_00_00_00_00_FF,
|
||||||
|
Two => !val & 0x00_00_00_00_FF_FF,
|
||||||
|
Four => !val & 0x00_00_FF_FF_FF_FF,
|
||||||
|
Eight => !val & 0x7F_FF_FF_FF_FF_FF,
|
||||||
|
Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
!val & 0x00_00_00_00_00_FF
|
||||||
|
} else if val <= 0xFF_FF {
|
||||||
|
!val & 0x00_00_00_00_FF_FF
|
||||||
|
} else if val <= 0xFF_FF_FF_FF {
|
||||||
|
!val & 0x00_00_FF_FF_FF_FF
|
||||||
|
} else {
|
||||||
|
!val & 0x7F_FF_FF_FF_FF_FF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This case shouldn't happen here, as it's handled before
|
||||||
|
Invalid => 0,
|
||||||
|
};
|
||||||
|
Value::Int { val: out_val, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only numerical values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
100
crates/nu-command/src/bits/or.rs
Normal file
100
crates/nu-command/src/bits/or.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits or"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits or")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit or",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise or for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic or"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits or to two numbers",
|
||||||
|
example: "2 | bits or 6",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 6,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical or to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits or 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val | target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits rol"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits rol")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a number with 2 bits",
|
||||||
|
example: "17 | bits rol 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 68,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a list of numbers with 2 bits",
|
||||||
|
example: "[5 3 2] | bits rol 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_left(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_left(val as u8, bits, span),
|
||||||
|
Two => get_rotate_left(val as u16, bits, span),
|
||||||
|
Four => get_rotate_left(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits ror"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits ror")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a number with 60 bits",
|
||||||
|
example: "17 | bits ror 60",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 272,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a list of numbers of one byte",
|
||||||
|
example: "[15 33 92] | bits ror 2 -n 1",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(195),
|
||||||
|
Value::test_int(72),
|
||||||
|
Value::test_int(23),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_right(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_right(val as u8, bits, span),
|
||||||
|
Two => get_rotate_right(val as u16, bits, span),
|
||||||
|
Four => get_rotate_right(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
188
crates/nu-command/src/bits/shift_left.rs
Normal file
188
crates/nu-command/src/bits/shift_left.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShl;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shl"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shl")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number by 7 bits",
|
||||||
|
example: "2 | bits shl 7",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 256,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number with 1 byte by 7 bits",
|
||||||
|
example: "2 | bits shl 7 -n 1",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a signed number by 1 bit",
|
||||||
|
example: "0x7F | bits shl 1 -s",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 254,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a list of numbers",
|
||||||
|
example: "[5 3 2] | bits shl 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shl(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift left {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_left(val as u8, bits, span),
|
||||||
|
Two => get_shift_left(val as u16, bits, span),
|
||||||
|
Four => get_shift_left(val as u32, bits, span),
|
||||||
|
Eight => get_shift_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
172
crates/nu-command/src/bits/shift_right.rs
Normal file
172
crates/nu-command/src/bits/shift_right.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShr;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shr"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shr")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift right a number with 2 bits",
|
||||||
|
example: "8 | bits shr 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 2,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift right a list of numbers",
|
||||||
|
example: "[15 35 2] | bits shr 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(3), Value::test_int(8), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shr(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift right {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_right(val as u8, bits, span),
|
||||||
|
Two => get_shift_right(val as u16, bits, span),
|
||||||
|
Four => get_shift_right(val as u32, bits, span),
|
||||||
|
Eight => get_shift_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
100
crates/nu-command/src/bits/xor.rs
Normal file
100
crates/nu-command/src/bits/xor.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits xor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits xor")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit xor",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise xor for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic xor"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits xor to two numbers",
|
||||||
|
example: "2 | bits xor 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical xor to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits xor 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val ^ target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
181
crates/nu-command/src/bytes/add.rs
Normal file
181
crates/nu-command/src/bytes/add.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
added_data: Vec<u8>,
|
||||||
|
index: Option<usize>,
|
||||||
|
end: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesAdd;
|
||||||
|
|
||||||
|
impl Command for BytesAdd {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes add"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes add")
|
||||||
|
.required("data", SyntaxShape::Binary, "the binary to add")
|
||||||
|
.named(
|
||||||
|
"index",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"index to insert binary data",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch("end", "add to the end of binary", Some('e'))
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally matches prefix of text by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Add specified bytes to the input"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["append", "truncate", "padding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
||||||
|
let end = call.has_flag("end");
|
||||||
|
|
||||||
|
let arg = Arguments {
|
||||||
|
added_data,
|
||||||
|
index,
|
||||||
|
end,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
|
||||||
|
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
|
||||||
|
example: "0x[FF AA AA] | bytes add 0x[11] -e",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0xAA, 0x11],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
|
||||||
|
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => add_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_impl(input: &[u8], args: &Arguments, span: Span) -> Value {
|
||||||
|
match args.index {
|
||||||
|
None => {
|
||||||
|
if args.end {
|
||||||
|
let mut added_data = args.added_data.clone();
|
||||||
|
let mut result = input.to_vec();
|
||||||
|
result.append(&mut added_data);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
} else {
|
||||||
|
let mut result = args.added_data.clone();
|
||||||
|
let mut input = input.to_vec();
|
||||||
|
result.append(&mut input);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(mut indx) => {
|
||||||
|
let inserted_index = if args.end {
|
||||||
|
input.len().saturating_sub(indx)
|
||||||
|
} else {
|
||||||
|
if indx > input.len() {
|
||||||
|
indx = input.len()
|
||||||
|
}
|
||||||
|
indx
|
||||||
|
};
|
||||||
|
let mut result = vec![];
|
||||||
|
let mut prev_data = input[..inserted_index].to_vec();
|
||||||
|
result.append(&mut prev_data);
|
||||||
|
let mut added_data = args.added_data.clone();
|
||||||
|
result.append(&mut added_data);
|
||||||
|
let mut after_data = input[inserted_index..].to_vec();
|
||||||
|
result.append(&mut after_data);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesAdd {})
|
||||||
|
}
|
||||||
|
}
|
295
crates/nu-command/src/bytes/at.rs
Normal file
295
crates/nu-command/src/bytes/at.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesAt;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
start: isize,
|
||||||
|
end: isize,
|
||||||
|
arg_span: Span,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
|
||||||
|
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
|
||||||
|
let (start, end, span) = match range {
|
||||||
|
Value::List { mut vals, span } => {
|
||||||
|
if vals.len() != 2 {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"More than two indices given".to_string(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let end = vals.pop().expect("Already check has size 2");
|
||||||
|
let end = match end {
|
||||||
|
Value::Int { val, .. } => val.to_string(),
|
||||||
|
Value::String { val, .. } => val,
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let start = vals.pop().expect("Already check has size 1");
|
||||||
|
let start = match start {
|
||||||
|
Value::Int { val, .. } => val.to_string(),
|
||||||
|
Value::String { val, .. } => val,
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(start, end, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String { val, span } => {
|
||||||
|
let splitted_result = val.split_once(',');
|
||||||
|
match splitted_result {
|
||||||
|
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
||||||
|
None => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start: isize = if start.is_empty() || start == "_" {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
match start.trim().parse() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let end: isize = if end.is_empty() || end == "_" {
|
||||||
|
isize::max_value()
|
||||||
|
} else {
|
||||||
|
match end.trim().parse() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"could not perform subbytes".to_string(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((start, end, span))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for BytesAt {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes at"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes at")
|
||||||
|
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally get bytes by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["slice"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let range: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
let (start, end, arg_span) = parse_range(range, call.head)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
arg_span,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Alternatively, you can use the form",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Drop the last `n` characters from the string",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x33, 0x44, 0x55],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the remaining characters from a starting index",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10, 0x01, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the characters from the beginning until ending index",
|
||||||
|
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x33, 0x44, 0x55, 0x10],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Or the characters from the beginning until ending index inside a table",
|
||||||
|
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x11, 0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => at_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let len: isize = input.len() as isize;
|
||||||
|
|
||||||
|
let start: isize = if arg.start < 0 {
|
||||||
|
arg.start + len
|
||||||
|
} else {
|
||||||
|
arg.start
|
||||||
|
};
|
||||||
|
let end: isize = if arg.end < 0 {
|
||||||
|
std::cmp::max(len + arg.end, 0)
|
||||||
|
} else {
|
||||||
|
arg.end
|
||||||
|
};
|
||||||
|
|
||||||
|
if start < len && end >= 0 {
|
||||||
|
match start.cmp(&end) {
|
||||||
|
Ordering::Equal => Value::Binary { val: vec![], span },
|
||||||
|
Ordering::Greater => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"End must be greater than or equal to Start".to_string(),
|
||||||
|
arg.arg_span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Ordering::Less => Value::Binary {
|
||||||
|
val: {
|
||||||
|
let input_iter = input.iter().copied().skip(start as usize);
|
||||||
|
if end == isize::max_value() {
|
||||||
|
input_iter.collect()
|
||||||
|
} else {
|
||||||
|
input_iter.take((end - start) as usize).collect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Binary { val: vec![], span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesAt {})
|
||||||
|
}
|
||||||
|
}
|
81
crates/nu-command/src/bytes/build_.rs
Normal file
81
crates/nu-command/src/bytes/build_.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use nu_engine::eval_expression;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesBuild;
|
||||||
|
|
||||||
|
impl Command for BytesBuild {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes build"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create bytes from the arguments."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["concatenate", "join"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("bytes build")
|
||||||
|
.rest("rest", SyntaxShape::Any, "list of bytes")
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
|
||||||
|
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x01, 0x02, 0x03, 0x04],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let mut output = vec![];
|
||||||
|
for expr in call.positional_iter() {
|
||||||
|
let val = eval_expression(engine_state, stack, expr)?;
|
||||||
|
match val {
|
||||||
|
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"only support expression which yields to binary data".to_string(),
|
||||||
|
other.span().unwrap_or(call.head),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesBuild {})
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_engine::get_full_help;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Bytes;
|
||||||
|
|
||||||
|
impl Command for Bytes {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes").category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Various commands for working with byte data"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Bytes;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Bytes {})
|
||||||
|
}
|
||||||
|
}
|
127
crates/nu-command/src/bytes/collect.rs
Normal file
127
crates/nu-command/src/bytes/collect.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct BytesCollect;
|
||||||
|
|
||||||
|
impl Command for BytesCollect {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes collect"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes collect")
|
||||||
|
.optional(
|
||||||
|
"separator",
|
||||||
|
SyntaxShape::Binary,
|
||||||
|
"optional separator to use when creating binary",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Concatenate multiple binary into a single binary, with an optional separator between each"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["join", "concatenate"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
|
||||||
|
// input should be a list of binary data.
|
||||||
|
let mut output_binary = vec![];
|
||||||
|
for value in input {
|
||||||
|
match value {
|
||||||
|
Value::Binary { mut val, .. } => {
|
||||||
|
output_binary.append(&mut val);
|
||||||
|
// manually concat
|
||||||
|
// TODO: make use of std::slice::Join when it's available in stable.
|
||||||
|
if let Some(sep) = &separator {
|
||||||
|
let mut work_sep = sep.clone();
|
||||||
|
output_binary.append(&mut work_sep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"The element type is {}, this command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(call.head),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match separator {
|
||||||
|
None => Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data()),
|
||||||
|
Some(sep) => {
|
||||||
|
if output_binary.is_empty() {
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
// have push one extra separator in previous step, pop them out.
|
||||||
|
for _ in sep {
|
||||||
|
let _ = output_binary.pop();
|
||||||
|
}
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: output_binary,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Create a byte array from input",
|
||||||
|
example: "[0x[11] 0x[13 15]] | bytes collect",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x11, 0x13, 0x15],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Create a byte array from input with a separator",
|
||||||
|
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesCollect {})
|
||||||
|
}
|
||||||
|
}
|
126
crates/nu-command/src/bytes/ends_with.rs
Normal file
126
crates/nu-command/src/bytes/ends_with.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesEndsWith;
|
||||||
|
|
||||||
|
impl Command for BytesEndsWith {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes ends-with"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes ends-with")
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally matches prefix of text by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check if bytes ends with a pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: true,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: true,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary ends with `0x[11]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: false,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Bool {
|
||||||
|
val: val.ends_with(&args.pattern),
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesEndsWith {})
|
||||||
|
}
|
||||||
|
}
|
224
crates/nu-command/src/bytes/index_of.rs
Normal file
224
crates/nu-command/src/bytes/index_of.rs
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
end: bool,
|
||||||
|
all: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesIndexOf;
|
||||||
|
|
||||||
|
impl Command for BytesIndexOf {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes index-of"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes index-of")
|
||||||
|
.required(
|
||||||
|
"pattern",
|
||||||
|
SyntaxShape::Binary,
|
||||||
|
"the pattern to find index of",
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally returns index of pattern in string by column paths",
|
||||||
|
)
|
||||||
|
.switch("all", "returns all matched index", Some('a'))
|
||||||
|
.switch("end", "search from the end of the binary", Some('e'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
end: call.has_flag("end"),
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern in bytes",
|
||||||
|
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
|
||||||
|
result: Some(Value::test_int(1)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern, search from end",
|
||||||
|
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
|
||||||
|
result: Some(Value::test_int(6)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns all matched index",
|
||||||
|
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns all matched index, searching from end",
|
||||||
|
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Returns index of pattern for specific column",
|
||||||
|
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(0),
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::test_int(-1),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => index_of_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
if arg.all {
|
||||||
|
search_all_index(input, &arg.pattern, arg.end, span)
|
||||||
|
} else {
|
||||||
|
let mut iter = input.windows(arg.pattern.len());
|
||||||
|
|
||||||
|
if arg.end {
|
||||||
|
Value::Int {
|
||||||
|
val: iter
|
||||||
|
.rev()
|
||||||
|
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||||
|
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
|
||||||
|
.unwrap_or(-1),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Int {
|
||||||
|
val: iter
|
||||||
|
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||||
|
.map(|x| x as i64)
|
||||||
|
.unwrap_or(-1),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
|
||||||
|
let mut result = vec![];
|
||||||
|
if from_end {
|
||||||
|
let (mut left, mut right) = (
|
||||||
|
input.len() as isize - pattern.len() as isize,
|
||||||
|
input.len() as isize,
|
||||||
|
);
|
||||||
|
while left >= 0 {
|
||||||
|
if &input[left as usize..right as usize] == pattern {
|
||||||
|
result.push(Value::Int {
|
||||||
|
val: left as i64,
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
left -= pattern.len() as isize;
|
||||||
|
right -= pattern.len() as isize;
|
||||||
|
} else {
|
||||||
|
left -= 1;
|
||||||
|
right -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::List { vals: result, span }
|
||||||
|
} else {
|
||||||
|
// doing find stuff.
|
||||||
|
let (mut left, mut right) = (0, pattern.len());
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = pattern.len();
|
||||||
|
while right <= input_len {
|
||||||
|
if &input[left..right] == pattern {
|
||||||
|
result.push(Value::Int {
|
||||||
|
val: left as i64,
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
} else {
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::List { vals: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesIndexOf {})
|
||||||
|
}
|
||||||
|
}
|
97
crates/nu-command/src/bytes/length.rs
Normal file
97
crates/nu-command/src/bytes/length.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesLen;
|
||||||
|
|
||||||
|
impl Command for BytesLen {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes length"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes length")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally find length of binary by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Output the length of any bytes in the pipeline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["size", "count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Return the lengths of multiple strings",
|
||||||
|
example: "0x[1F FF AA AB] | bytes length",
|
||||||
|
result: Some(Value::test_int(4)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Return the lengths of multiple strings",
|
||||||
|
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(4), Value::test_int(1)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Int {
|
||||||
|
val: val.len() as i64,
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesLen {})
|
||||||
|
}
|
||||||
|
}
|
25
crates/nu-command/src/bytes/mod.rs
Normal file
25
crates/nu-command/src/bytes/mod.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
mod add;
|
||||||
|
mod at;
|
||||||
|
mod build_;
|
||||||
|
mod bytes_;
|
||||||
|
mod collect;
|
||||||
|
mod ends_with;
|
||||||
|
mod index_of;
|
||||||
|
mod length;
|
||||||
|
mod remove;
|
||||||
|
mod replace;
|
||||||
|
mod reverse;
|
||||||
|
mod starts_with;
|
||||||
|
|
||||||
|
pub use add::BytesAdd;
|
||||||
|
pub use at::BytesAt;
|
||||||
|
pub use build_::BytesBuild;
|
||||||
|
pub use bytes_::Bytes;
|
||||||
|
pub use collect::BytesCollect;
|
||||||
|
pub use ends_with::BytesEndsWith;
|
||||||
|
pub use index_of::BytesIndexOf;
|
||||||
|
pub use length::BytesLen;
|
||||||
|
pub use remove::BytesRemove;
|
||||||
|
pub use replace::BytesReplace;
|
||||||
|
pub use reverse::BytesReverse;
|
||||||
|
pub use starts_with::BytesStartsWith;
|
210
crates/nu-command/src/bytes/remove.rs
Normal file
210
crates/nu-command/src/bytes/remove.rs
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
end: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesRemove;
|
||||||
|
|
||||||
|
impl Command for BytesRemove {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes remove"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes remove")
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally remove bytes by column paths",
|
||||||
|
)
|
||||||
|
.switch("end", "remove from end of binary", Some('e'))
|
||||||
|
.switch("all", "remove occurrences of finding binary", Some('a'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Remove bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["search", "shift", "switch"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
|
if pattern_to_remove.item.is_empty() {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"the pattern to remove cannot be empty".to_string(),
|
||||||
|
pattern_to_remove.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern: pattern_to_remove,
|
||||||
|
end: call.has_flag("end"),
|
||||||
|
cell_paths,
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Remove contents",
|
||||||
|
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove all occurrences of find binary",
|
||||||
|
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xBB],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove occurrences of find binary from end",
|
||||||
|
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Remove all occurrences of find binary in table",
|
||||||
|
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x17, 0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => remove_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let mut result = vec![];
|
||||||
|
let remove_all = arg.all;
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = arg.pattern.len();
|
||||||
|
|
||||||
|
// Note:
|
||||||
|
// remove_all from start and end will generate the same result.
|
||||||
|
// so we'll put `remove_all` relative logic into else clouse.
|
||||||
|
if arg.end && !remove_all {
|
||||||
|
let (mut left, mut right) = (
|
||||||
|
input.len() as isize - arg.pattern.len() as isize,
|
||||||
|
input.len() as isize,
|
||||||
|
);
|
||||||
|
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
|
||||||
|
result.push(input[right as usize - 1]);
|
||||||
|
left -= 1;
|
||||||
|
right -= 1;
|
||||||
|
}
|
||||||
|
// append the remaining thing to result, this can be happeneed when
|
||||||
|
// we have something to remove and remove_all is False.
|
||||||
|
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||||
|
result.append(&mut remain);
|
||||||
|
result = result.into_iter().rev().collect();
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
} else {
|
||||||
|
let (mut left, mut right) = (0, arg.pattern.len());
|
||||||
|
while right <= input_len {
|
||||||
|
if input[left..right] == arg.pattern {
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
if !remove_all {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(input[left]);
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append the remaing thing to result, this can happened when
|
||||||
|
// we have something to remove and remove_all is False.
|
||||||
|
let mut remain = input[left..].to_vec();
|
||||||
|
result.append(&mut remain);
|
||||||
|
Value::Binary { val: result, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesRemove {})
|
||||||
|
}
|
||||||
|
}
|
185
crates/nu-command/src/bytes/replace.rs
Normal file
185
crates/nu-command/src/bytes/replace.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Call, CellPath},
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
find: Vec<u8>,
|
||||||
|
replace: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesReplace;
|
||||||
|
|
||||||
|
impl Command for BytesReplace {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes replace"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes replace")
|
||||||
|
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||||
|
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally find and replace text by column paths",
|
||||||
|
)
|
||||||
|
.switch("all", "replace all occurrences of find binary", Some('a'))
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Find and replace binary"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["search", "shift", "switch"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
|
if find.item.is_empty() {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"the pattern to find cannot be empty".to_string(),
|
||||||
|
find.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = Arguments {
|
||||||
|
find: find.item,
|
||||||
|
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
||||||
|
cell_paths,
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
};
|
||||||
|
|
||||||
|
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Find and replace contents",
|
||||||
|
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Find and replace all occurrences of find binary",
|
||||||
|
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Find and replace all occurrences of find binary in table",
|
||||||
|
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x13, 0x12, 0x13],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x14, 0x15, 0x16],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Binary {
|
||||||
|
val: vec![0x17, 0x18, 0x19],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => replace_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
|
let mut replaced = vec![];
|
||||||
|
let replace_all = arg.all;
|
||||||
|
|
||||||
|
// doing find-and-replace stuff.
|
||||||
|
let (mut left, mut right) = (0, arg.find.len());
|
||||||
|
let input_len = input.len();
|
||||||
|
let pattern_len = arg.find.len();
|
||||||
|
while right <= input_len {
|
||||||
|
if input[left..right] == arg.find {
|
||||||
|
let mut to_replace = arg.replace.clone();
|
||||||
|
replaced.append(&mut to_replace);
|
||||||
|
left += pattern_len;
|
||||||
|
right += pattern_len;
|
||||||
|
if !replace_all {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replaced.push(input[left]);
|
||||||
|
left += 1;
|
||||||
|
right += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut remain = input[left..].to_vec();
|
||||||
|
replaced.append(&mut remain);
|
||||||
|
Value::Binary {
|
||||||
|
val: replaced,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesReplace {})
|
||||||
|
}
|
||||||
|
}
|
105
crates/nu-command/src/bytes/reverse.rs
Normal file
105
crates/nu-command/src/bytes/reverse.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesReverse;
|
||||||
|
|
||||||
|
impl Command for BytesReverse {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes reverse"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes reverse")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally matches prefix of text by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Reverse every bytes in the pipeline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["convert", "inverse", "flip"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Reverse bytes `0x[1F FF AA AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes reverse",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Reverse bytes `0x[FF AA AA]`",
|
||||||
|
example: "0x[FF AA AA] | bytes reverse",
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![0xAA, 0xAA, 0xFF],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => {
|
||||||
|
let mut reversed_input = val.to_vec();
|
||||||
|
reversed_input.reverse();
|
||||||
|
Value::Binary {
|
||||||
|
val: reversed_input,
|
||||||
|
span: *val_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesReverse {})
|
||||||
|
}
|
||||||
|
}
|
132
crates/nu-command/src/bytes/starts_with.rs
Normal file
132
crates/nu-command/src/bytes/starts_with.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::ast::CellPath;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
pattern: Vec<u8>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
|
||||||
|
pub struct BytesStartsWith;
|
||||||
|
|
||||||
|
impl Command for BytesStartsWith {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes starts-with"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes starts-with")
|
||||||
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally matches prefix of text by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check if bytes starts with a pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["pattern", "match", "find", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let arg = Arguments {
|
||||||
|
pattern,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(
|
||||||
|
starts_with,
|
||||||
|
arg,
|
||||||
|
input,
|
||||||
|
call.head,
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F FF AA]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: true,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: true,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||||
|
result: Some(Value::Bool {
|
||||||
|
val: false,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Bool {
|
||||||
|
val: val.starts_with(&args.pattern),
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesStartsWith {})
|
||||||
|
}
|
||||||
|
}
|
@ -215,8 +215,8 @@ fn histogram_impl(
|
|||||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter() {
|
for (val, count) in counter.into_iter() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -96,31 +97,12 @@ fn fmt(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => fmt_it(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
Value::Filesize { val, .. } => fmt_it(*val, span),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -29,7 +30,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "binary", "bytes", "bin"]
|
vec!["convert", "bytes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -100,7 +101,7 @@ fn into_binary(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
||||||
@ -120,27 +121,10 @@ fn into_binary(
|
|||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
_ => input.map(
|
_ => {
|
||||||
move |v| {
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
if column_paths.is_empty() {
|
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
action(&v, head)
|
}
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +144,7 @@ fn float_to_endian(n: f64) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { .. } => input.clone(),
|
Value::Binary { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Binary {
|
Value::Int { val, .. } => Value::Binary {
|
||||||
@ -180,7 +164,7 @@ pub fn action(input: &Value, span: Span) -> Value {
|
|||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Bool { val, .. } => Value::Binary {
|
Value::Bool { val, .. } => Value::Binary {
|
||||||
val: int_to_endian(if *val { 1i64 } else { 0 }),
|
val: int_to_endian(i64::from(*val)),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Date { val, .. } => Value::Binary {
|
Value::Date { val, .. } => Value::Binary {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -108,28 +109,9 @@ fn into_bool(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
||||||
@ -154,7 +136,7 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Bool { .. } => input.clone(),
|
Value::Bool { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Bool {
|
Value::Int { val, .. } => Value::Bool {
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generate_strftime_list;
|
|
||||||
use crate::parse_date_from_string;
|
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
timezone: Option<Spanned<String>>,
|
zone_options: Option<Spanned<Zone>>,
|
||||||
offset: Option<Spanned<i64>>,
|
format_options: Option<DatetimeFormat>,
|
||||||
format: Option<String>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
column_paths: Vec<CellPath>,
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case it may be confused with chrono::TimeZone
|
// In case it may be confused with chrono::TimeZone
|
||||||
@ -97,7 +102,36 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
if call.has_flag("list") {
|
||||||
|
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
// if zone-offset is specified, then zone will be neglected
|
||||||
|
let timezone = call.get_flag::<Spanned<String>>(engine_state, stack, "timezone")?;
|
||||||
|
let zone_options =
|
||||||
|
match &call.get_flag::<Spanned<i64>>(engine_state, stack, "offset")? {
|
||||||
|
Some(zone_offset) => Some(Spanned {
|
||||||
|
item: Zone::new(zone_offset.item),
|
||||||
|
span: zone_offset.span,
|
||||||
|
}),
|
||||||
|
None => timezone.as_ref().map(|zone| Spanned {
|
||||||
|
item: Zone::from_string(zone.item.clone()),
|
||||||
|
span: zone.span,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let format_options = call
|
||||||
|
.get_flag::<String>(engine_state, stack, "format")?
|
||||||
|
.as_ref()
|
||||||
|
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
format_options,
|
||||||
|
zone_options,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -105,7 +139,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "date", "time", "timezone", "UTC"]
|
vec!["convert", "timezone", "UTC"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -148,6 +182,15 @@ impl Command for SubCommand {
|
|||||||
example: "1614434140 | into datetime -o +9",
|
example: "1614434140 | into datetime -o +9",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Convert timestamps like the sqlite history t",
|
||||||
|
example: "1656165681720 | into datetime",
|
||||||
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp_millis(1656165681720).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,72 +198,9 @@ impl Command for SubCommand {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DatetimeFormat(String);
|
struct DatetimeFormat(String);
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
let timezone = &args.zone_options;
|
||||||
stack: &mut Stack,
|
let dateformat = &args.format_options;
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let options = Arguments {
|
|
||||||
timezone: call.get_flag(engine_state, stack, "timezone")?,
|
|
||||||
offset: call.get_flag(engine_state, stack, "offset")?,
|
|
||||||
format: call.get_flag(engine_state, stack, "format")?,
|
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if zone-offset is specified, then zone will be neglected
|
|
||||||
let zone_options = match &options.offset {
|
|
||||||
Some(zone_offset) => Some(Spanned {
|
|
||||||
item: Zone::new(zone_offset.item),
|
|
||||||
span: zone_offset.span,
|
|
||||||
}),
|
|
||||||
None => options.timezone.as_ref().map(|zone| Spanned {
|
|
||||||
item: Zone::from_string(zone.item.clone()),
|
|
||||||
span: zone.span,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let list_flag = call.has_flag("list");
|
|
||||||
|
|
||||||
let format_options = options
|
|
||||||
.format
|
|
||||||
.as_ref()
|
|
||||||
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if options.column_paths.is_empty() && !list_flag {
|
|
||||||
action(&v, &zone_options, &format_options, head)
|
|
||||||
} else if list_flag {
|
|
||||||
generate_strftime_list(head, true)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &options.column_paths {
|
|
||||||
let zone_options = zone_options.clone();
|
|
||||||
let format_options = format_options.clone();
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, &zone_options, &format_options, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(
|
|
||||||
input: &Value,
|
|
||||||
timezone: &Option<Spanned<Zone>>,
|
|
||||||
dateformat: &Option<DatetimeFormat>,
|
|
||||||
head: Span,
|
|
||||||
) -> Value {
|
|
||||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
Value::Int { val, .. } => Ok(*val),
|
Value::Int { val, .. } => Ok(*val),
|
||||||
@ -251,10 +231,19 @@ fn action(
|
|||||||
|
|
||||||
return match timezone {
|
return match timezone {
|
||||||
// default to UTC
|
// default to UTC
|
||||||
None => Value::Date {
|
None => {
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
// be able to convert chrono::Utc::now()
|
||||||
span: head,
|
let dt = match ts.to_string().len() {
|
||||||
},
|
x if x > 13 => Utc.timestamp_nanos(ts).into(),
|
||||||
|
x if x > 10 => Utc.timestamp_millis(ts).into(),
|
||||||
|
_ => Utc.timestamp(ts, 0).into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Date {
|
||||||
|
val: dt,
|
||||||
|
span: head,
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(Spanned { item, span }) => match item {
|
Some(Spanned { item, span }) => match item {
|
||||||
Zone::Utc => Value::Date {
|
Zone::Utc => Value::Date {
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
val: Utc.timestamp(ts, 0).into(),
|
||||||
@ -295,7 +284,7 @@ fn action(
|
|||||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||||
Ok(d) => Value::Date { val: d, span: head },
|
Ok(d) => Value::Date { val: d, span: head },
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
return Value::Error {
|
Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
format!("could not parse as datetime using format '{}'", dt.0),
|
format!("could not parse as datetime using format '{}'", dt.0),
|
||||||
reason.to_string(),
|
reason.to_string(),
|
||||||
@ -343,7 +332,12 @@ mod tests {
|
|||||||
fn takes_a_date_format() {
|
fn takes_a_date_format() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -355,7 +349,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_iso8601_date_format() {
|
fn takes_iso8601_date_format() {
|
||||||
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
||||||
let actual = action(&date_str, &None, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -371,7 +370,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -388,7 +392,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_int, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_int, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -405,7 +414,12 @@ mod tests {
|
|||||||
item: Zone::Local,
|
item: Zone::Local,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Local.timestamp(1614434140, 0).into(),
|
val: Local.timestamp(1614434140, 0).into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
@ -417,8 +431,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp_without_timezone() {
|
fn takes_timestamp_without_timezone() {
|
||||||
let date_str = Value::test_string("1614434140");
|
let date_str = Value::test_string("1614434140");
|
||||||
let timezone_option = None;
|
let args = Arguments {
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
@ -435,7 +453,12 @@ mod tests {
|
|||||||
item: Zone::Utc,
|
item: Zone::Utc,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -444,7 +467,12 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -36,13 +37,15 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer in table",
|
description: "Convert string to decimal in table",
|
||||||
example: "[[num]; ['5.01']] | into decimal num",
|
example: "[[num]; ['5.01']] | into decimal num",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![Value::Record {
|
vals: vec![Value::Record {
|
||||||
@ -54,50 +57,25 @@ impl Command for SubCommand {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer",
|
description: "Convert string to decimal",
|
||||||
example: "'1.345' | into decimal",
|
example: "'1.345' | into decimal",
|
||||||
result: Some(Value::test_float(1.345)),
|
result: Some(Value::test_float(1.345)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert decimal to integer",
|
description: "Convert decimal to decimal",
|
||||||
example: "'-5.9' | into decimal",
|
example: "'-5.9' | into decimal",
|
||||||
result: Some(Value::test_float(-5.9)),
|
result: Some(Value::test_float(-5.9)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert boolean to decimal",
|
||||||
|
example: "true | into decimal",
|
||||||
|
result: Some(Value::test_float(1.0)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(input: &Value, head: Span) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::String { val: s, span } => {
|
Value::String { val: s, span } => {
|
||||||
let other = s.trim();
|
let other = s.trim();
|
||||||
@ -118,6 +96,13 @@ fn action(input: &Value, head: Span) -> Value {
|
|||||||
val: *v as f64,
|
val: *v as f64,
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
|
Value::Bool { val: b, span } => Value::Float {
|
||||||
|
val: match b {
|
||||||
|
true => 1.0,
|
||||||
|
false => 0.0,
|
||||||
|
},
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
other => {
|
other => {
|
||||||
let span = other.span();
|
let span = other.span();
|
||||||
match span {
|
match span {
|
||||||
@ -151,7 +136,7 @@ mod tests {
|
|||||||
let word = Value::test_string("3.1415");
|
let word = Value::test_string("3.1415");
|
||||||
let expected = Value::test_float(3.1415);
|
let expected = Value::test_float(3.1415);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data());
|
let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data());
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +144,11 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
||||||
let decimal_str = Value::test_string("11.6anra");
|
let decimal_str = Value::test_string("11.6anra");
|
||||||
|
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -168,7 +157,11 @@ mod tests {
|
|||||||
fn int_to_decimal() {
|
fn int_to_decimal() {
|
||||||
let decimal_str = Value::test_int(10);
|
let decimal_str = Value::test_int(10);
|
||||||
let expected = Value::test_float(10.0);
|
let expected = Value::test_float(10.0);
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath, Expr},
|
ast::{Call, CellPath, Expr},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,6 +17,12 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
|
.named(
|
||||||
|
"convert",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"convert duration into another duration",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -28,6 +35,10 @@ impl Command for SubCommand {
|
|||||||
"Convert value to duration"
|
"Convert value to duration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"into duration does not take leap years into account and every month is calculated with 30 days"
|
||||||
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "time", "period"]
|
vec!["convert", "time", "period"]
|
||||||
}
|
}
|
||||||
@ -102,6 +113,22 @@ impl Command for SubCommand {
|
|||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert string to the requested duration as a string",
|
||||||
|
example: "'7min' | into duration --convert sec",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "420 sec".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert duration to the requested duration as a string",
|
||||||
|
example: "420sec | into duration --convert min",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "7 min".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,17 +140,23 @@ fn into_duration(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
let float_precision = config.float_precision as usize;
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
action(&v, head)
|
action(&v, &convert_to_unit, float_precision, head)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
let r =
|
let d = convert_to_unit.clone();
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
let r = ret.update_cell_path(
|
||||||
|
&path.members,
|
||||||
|
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||||
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
return Value::Error { error };
|
||||||
}
|
}
|
||||||
@ -136,7 +169,151 @@ fn into_duration(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
fn convert_str_from_unit_to_unit(
|
||||||
|
val: i64,
|
||||||
|
from_unit: &str,
|
||||||
|
to_unit: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<f64, ShellError> {
|
||||||
|
match (from_unit, to_unit) {
|
||||||
|
("ns", "ns") => Ok(val as f64),
|
||||||
|
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||||
|
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
|
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||||
|
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||||
|
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("ns", "dec") => {
|
||||||
|
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||||
|
("us", "us") => Ok(val as f64),
|
||||||
|
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||||
|
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
|
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||||
|
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
|
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||||
|
("ms", "ms") => Ok(val as f64),
|
||||||
|
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||||
|
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||||
|
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||||
|
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
|
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||||
|
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
|
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||||
|
("sec", "sec") => Ok(val as f64),
|
||||||
|
("sec", "min") => Ok(val as f64 / 60.0),
|
||||||
|
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||||
|
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||||
|
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
|
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
|
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||||
|
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||||
|
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||||
|
("min", "sec") => Ok(val as f64 * 60.0),
|
||||||
|
("min", "min") => Ok(val as f64),
|
||||||
|
("min", "hr") => Ok(val as f64 / 60.0),
|
||||||
|
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||||
|
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||||
|
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||||
|
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||||
|
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||||
|
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||||
|
("hr", "min") => Ok(val as f64 * 60.0),
|
||||||
|
("hr", "hr") => Ok(val as f64),
|
||||||
|
("hr", "day") => Ok(val as f64 / 24.0),
|
||||||
|
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||||
|
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||||
|
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||||
|
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
|
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||||
|
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||||
|
("day", "hr") => Ok(val as f64 * 24.0),
|
||||||
|
("day", "day") => Ok(val as f64),
|
||||||
|
("day", "wk") => Ok(val as f64 / 7.0),
|
||||||
|
("day", "month") => Ok(val as f64 / 30.0),
|
||||||
|
("day", "yr") => Ok(val as f64 / 365.0),
|
||||||
|
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||||
|
|
||||||
|
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||||
|
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||||
|
("wk", "day") => Ok(val as f64 * 7.0),
|
||||||
|
("wk", "wk") => Ok(val as f64),
|
||||||
|
("wk", "month") => Ok(val as f64 / 4.0),
|
||||||
|
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||||
|
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||||
|
|
||||||
|
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||||
|
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||||
|
("month", "day") => Ok(val as f64 * 30.0),
|
||||||
|
("month", "wk") => Ok(val as f64 * 4.0),
|
||||||
|
("month", "month") => Ok(val as f64),
|
||||||
|
("month", "yr") => Ok(val as f64 / 12.0),
|
||||||
|
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||||
|
|
||||||
|
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||||
|
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||||
|
("yr", "day") => Ok(val as f64 * 365.0),
|
||||||
|
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||||
|
("yr", "month") => Ok(val as f64 * 12.0),
|
||||||
|
("yr", "yr") => Ok(val as f64),
|
||||||
|
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||||
|
|
||||||
|
_ => Err(ShellError::CantConvertWithValue(
|
||||||
|
"string duration".to_string(),
|
||||||
|
"string duration".to_string(),
|
||||||
|
to_unit.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
if let Expr::Int(x) = value.expr {
|
if let Expr::Int(x) = value.expr {
|
||||||
@ -155,21 +332,139 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ShellError::CantConvert(
|
Err(ShellError::CantConvertWithValue(
|
||||||
"duration".to_string(),
|
"duration".to_string(),
|
||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
span,
|
span,
|
||||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn string_to_unit_duration(
|
||||||
|
s: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<(&str, i64), ShellError> {
|
||||||
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
|
if let Expr::Int(x) = value.expr {
|
||||||
|
match unit.item {
|
||||||
|
Unit::Nanosecond => return Ok(("ns", x)),
|
||||||
|
Unit::Microsecond => return Ok(("us", x)),
|
||||||
|
Unit::Millisecond => return Ok(("ms", x)),
|
||||||
|
Unit::Second => return Ok(("sec", x)),
|
||||||
|
Unit::Minute => return Ok(("min", x)),
|
||||||
|
Unit::Hour => return Ok(("hr", x)),
|
||||||
|
Unit::Day => return Ok(("day", x)),
|
||||||
|
Unit::Week => return Ok(("wk", x)),
|
||||||
|
|
||||||
|
_ => return Ok(("ns", 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ShellError::CantConvertWithValue(
|
||||||
|
"duration".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(
|
||||||
|
input: &Value,
|
||||||
|
convert_to_unit: &Option<Spanned<String>>,
|
||||||
|
float_precision: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration {
|
||||||
Value::String { val, .. } => match string_to_duration(val, span) {
|
val: val_num,
|
||||||
Ok(val) => Value::Duration { val, span },
|
span: value_span,
|
||||||
Err(error) => Value::Error { error },
|
} => {
|
||||||
},
|
if let Some(to_unit) = convert_to_unit {
|
||||||
|
let from_unit = "ns";
|
||||||
|
let duration = *val_num;
|
||||||
|
match convert_str_from_unit_to_unit(
|
||||||
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
|
span,
|
||||||
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String {
|
||||||
|
val,
|
||||||
|
span: value_span,
|
||||||
|
} => {
|
||||||
|
if let Some(to_unit) = convert_to_unit {
|
||||||
|
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
|
||||||
|
let from_unit = dur.0;
|
||||||
|
let duration = dur.1;
|
||||||
|
match convert_str_from_unit_to_unit(
|
||||||
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
|
span,
|
||||||
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"'into duration' does not support this string input".into(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match string_to_duration(val, span, *value_span) {
|
||||||
|
Ok(val) => Value::Duration { val, span },
|
||||||
|
Err(error) => Value::Error { error },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Value::Error {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"'into duration' does not support this input".into(),
|
"'into duration' does not support this input".into(),
|
||||||
@ -195,8 +490,9 @@ mod test {
|
|||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let word = Value::test_string("3ns");
|
let word = Value::test_string("3ns");
|
||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,8 +504,9 @@ mod test {
|
|||||||
val: 4 * 1000,
|
val: 4 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,8 +518,9 @@ mod test {
|
|||||||
val: 5 * 1000 * 1000,
|
val: 5 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,8 +532,9 @@ mod test {
|
|||||||
val: 1000 * 1000 * 1000,
|
val: 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,8 +546,9 @@ mod test {
|
|||||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,8 +560,9 @@ mod test {
|
|||||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,8 +574,9 @@ mod test {
|
|||||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,8 +588,9 @@ mod test {
|
|||||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -28,7 +29,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "number", "size", "bytes"]
|
vec!["convert", "number", "bytes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -38,7 +39,9 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
into_filesize(engine_state, stack, call, input)
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -84,37 +87,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_filesize(
|
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
|
||||||
if let Ok(value_span) = input.span() {
|
if let Ok(value_span) = input.span() {
|
||||||
match input {
|
match input {
|
||||||
Value::Filesize { .. } => input.clone(),
|
Value::Filesize { .. } => input.clone(),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -6,8 +7,15 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
radix: Option<Value>,
|
radix: u32,
|
||||||
column_paths: Vec<CellPath>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
little_endian: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -21,6 +29,7 @@ impl Command for SubCommand {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into int")
|
Signature::build("into int")
|
||||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||||
|
.switch("little-endian", "use little-endian byte decoding", None)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -44,7 +53,29 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
into_int(engine_state, stack, call, input)
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
let radix = call.get_flag::<Value>(engine_state, stack, "radix")?;
|
||||||
|
let radix: u32 = match radix {
|
||||||
|
Some(Value::Int { val, span }) => {
|
||||||
|
if !(2..=36).contains(&val) {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Radix must lie in the range [2, 36]".to_string(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
val as u32
|
||||||
|
}
|
||||||
|
Some(_) => 10,
|
||||||
|
None => 10,
|
||||||
|
};
|
||||||
|
let args = Arguments {
|
||||||
|
radix,
|
||||||
|
little_endian: call.has_flag("little-endian"),
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -100,62 +131,28 @@ impl Command for SubCommand {
|
|||||||
example: "'FF' | into int -r 16",
|
example: "'FF' | into int -r 16",
|
||||||
result: Some(Value::test_int(255)),
|
result: Some(Value::test_int(255)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert octal string to integer",
|
||||||
|
example: "'0o10132' | into int",
|
||||||
|
result: Some(Value::test_int(4186)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert 0 padded string to integer",
|
||||||
|
example: "'0010132' | into int",
|
||||||
|
result: Some(Value::test_int(10132)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert 0 padded string to integer with radix",
|
||||||
|
example: "'0010132' | into int -r 8",
|
||||||
|
result: Some(Value::test_int(4186)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_int(
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
let radix = args.radix;
|
||||||
stack: &mut Stack,
|
let little_endian = args.little_endian;
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let options = Arguments {
|
|
||||||
radix: call.get_flag(engine_state, stack, "radix")?,
|
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let radix: u32 = match options.radix {
|
|
||||||
Some(Value::Int { val, .. }) => val as u32,
|
|
||||||
Some(_) => 10,
|
|
||||||
None => 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(val) = &options.radix {
|
|
||||||
if !(2..=36).contains(&radix) {
|
|
||||||
return Err(ShellError::UnsupportedInput(
|
|
||||||
"Radix must lie in the range [2, 36]".to_string(),
|
|
||||||
val.span()?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if options.column_paths.is_empty() {
|
|
||||||
action(&v, head, radix)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &options.column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, head, radix)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val: _, .. } => {
|
Value::Int { val: _, .. } => {
|
||||||
if radix == 10 {
|
if radix == 10 {
|
||||||
@ -166,7 +163,32 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
|||||||
}
|
}
|
||||||
Value::Filesize { val, .. } => Value::Int { val: *val, span },
|
Value::Filesize { val, .. } => Value::Int { val: *val, span },
|
||||||
Value::Float { val, .. } => Value::Int {
|
Value::Float { val, .. } => Value::Int {
|
||||||
val: *val as i64,
|
val: {
|
||||||
|
if radix == 10 {
|
||||||
|
*val as i64
|
||||||
|
} else {
|
||||||
|
match convert_int(
|
||||||
|
&Value::Int {
|
||||||
|
val: *val as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
radix,
|
||||||
|
)
|
||||||
|
.as_i64()
|
||||||
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
_ => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"Could not convert float to integer".to_string(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
@ -190,8 +212,38 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
|||||||
val: val.timestamp(),
|
val: val.timestamp(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Binary { val, span } => {
|
||||||
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
let mut val = val.to_vec();
|
||||||
|
|
||||||
|
if little_endian {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.push(0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
|
Value::Int {
|
||||||
|
val: LittleEndian::read_i64(&val),
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.insert(0, 0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
|
Value::Int {
|
||||||
|
val: BigEndian::read_i64(&val),
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Value::Error {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput("'into int' for unsupported type".into(), span),
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!("'into int' for unsupported type '{}'", input.get_type()),
|
||||||
|
span,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,11 +252,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
|||||||
let i = match input {
|
let i = match input {
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
if val.starts_with("0x") || val.starts_with("0b") {
|
let val = val.trim();
|
||||||
|
if val.starts_with("0x") // hex
|
||||||
|
|| val.starts_with("0b") // binary
|
||||||
|
|| val.starts_with("0o")
|
||||||
|
// octal
|
||||||
|
{
|
||||||
match int_from_string(val, head) {
|
match int_from_string(val, head) {
|
||||||
Ok(x) => return Value::Int { val: x, span: head },
|
Ok(x) => return Value::Int { val: x, span: head },
|
||||||
Err(e) => return Value::Error { error: e },
|
Err(e) => return Value::Error { error: e },
|
||||||
}
|
}
|
||||||
|
} else if val.starts_with("00") {
|
||||||
|
// It's a padded string
|
||||||
|
match i64::from_str_radix(val, radix) {
|
||||||
|
Ok(n) => return Value::Int { val: n, span: head },
|
||||||
|
Err(e) => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::CantConvert(
|
||||||
|
"string".to_string(),
|
||||||
|
"int".to_string(),
|
||||||
|
head,
|
||||||
|
Some(e.to_string()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val.to_string()
|
val.to_string()
|
||||||
}
|
}
|
||||||
@ -217,10 +289,10 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match i64::from_str_radix(&i, radix) {
|
match i64::from_str_radix(i.trim(), radix) {
|
||||||
Ok(n) => Value::Int { val: n, span: head },
|
Ok(n) => Value::Int { val: n, span: head },
|
||||||
Err(_reason) => Value::Error {
|
Err(_reason) => Value::Error {
|
||||||
error: ShellError::CantConvert("int".to_string(), "string".to_string(), head, None),
|
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,7 +330,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
};
|
};
|
||||||
Ok(num)
|
Ok(num)
|
||||||
}
|
}
|
||||||
_ => match a_string.parse::<i64>() {
|
o if o.starts_with("0o") => {
|
||||||
|
let num = match i64::from_str_radix(o.trim_start_matches("0o"), 8) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_reason) => {
|
||||||
|
return Err(ShellError::CantConvert(
|
||||||
|
"int".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
span,
|
||||||
|
Some(r#"octal digits following "0o" should be in 0-7"#.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(num)
|
||||||
|
}
|
||||||
|
_ => match trimmed.parse::<i64>() {
|
||||||
Ok(n) => Ok(n),
|
Ok(n) => Ok(n),
|
||||||
Err(_) => match a_string.parse::<f64>() {
|
Err(_) => match a_string.parse::<f64>() {
|
||||||
Ok(f) => Ok(f as i64),
|
Ok(f) => Ok(f as i64),
|
||||||
@ -266,7 +352,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
"int".to_string(),
|
"int".to_string(),
|
||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
span,
|
span,
|
||||||
None,
|
Some(format!(
|
||||||
|
r#"string "{}" does not represent a valid integer"#,
|
||||||
|
trimmed
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -291,21 +380,45 @@ mod test {
|
|||||||
let word = Value::test_string("10");
|
let word = Value::test_string("10");
|
||||||
let expected = Value::test_int(10);
|
let expected = Value::test_int(10);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data(), 10);
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_binary_to_integer() {
|
fn turns_binary_to_integer() {
|
||||||
let s = Value::test_string("0b101");
|
let s = Value::test_string("0b101");
|
||||||
let actual = action(&s, Span::test_data(), 10);
|
let actual = action(
|
||||||
|
&s,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, Value::test_int(5));
|
assert_eq!(actual, Value::test_int(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_hex_to_integer() {
|
fn turns_hex_to_integer() {
|
||||||
let s = Value::test_string("0xFF");
|
let s = Value::test_string("0xFF");
|
||||||
let actual = action(&s, Span::test_data(), 16);
|
let actual = action(
|
||||||
|
&s,
|
||||||
|
&Arguments {
|
||||||
|
radix: 16,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, Value::test_int(255));
|
assert_eq!(actual, Value::test_int(255));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +426,15 @@ mod test {
|
|||||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||||
let integer_str = Value::test_string("36anra");
|
let integer_str = Value::test_string("36anra");
|
||||||
|
|
||||||
let actual = action(&integer_str, Span::test_data(), 10);
|
let actual = action(
|
||||||
|
&integer_str,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error)
|
assert_eq!(actual.get_type(), Error)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||||
SyntaxShape, Value,
|
Span, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::get_system_locale;
|
||||||
|
use num_format::ToFormattedString;
|
||||||
|
|
||||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
struct Arguments {
|
||||||
|
decimals_value: Option<i64>,
|
||||||
|
decimals: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
@ -38,7 +52,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "str", "text"]
|
vec!["convert", "text"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -53,6 +67,14 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "convert integer to string and append three decimal places",
|
||||||
|
example: "5 | into string -d 3",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "5.000".to_string(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string and round to nearest integer",
|
description: "convert decimal to string and round to nearest integer",
|
||||||
example: "1.7 | into string -d 0",
|
example: "1.7 | into string -d 0",
|
||||||
@ -141,9 +163,6 @@ fn string_helper(
|
|||||||
let decimals = call.has_flag("decimals");
|
let decimals = call.has_flag("decimals");
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let config = engine_state.get_config().clone();
|
|
||||||
|
|
||||||
if let Some(decimal_val) = decimals_value {
|
if let Some(decimal_val) = decimals_value {
|
||||||
if decimals && decimal_val.is_negative() {
|
if decimals && decimal_val.is_negative() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
@ -152,6 +171,15 @@ fn string_helper(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let config = engine_state.get_config().clone();
|
||||||
|
let args = Arguments {
|
||||||
|
decimals_value,
|
||||||
|
decimals,
|
||||||
|
cell_paths,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
|
||||||
@ -171,49 +199,18 @@ fn string_helper(
|
|||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
_ => input.map(
|
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head, decimals, decimals_value, false, &config)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let config = config.clone();
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| {
|
|
||||||
action(old, head, decimals, decimals_value, false, &config)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
input: &Value,
|
let decimals = args.decimals;
|
||||||
span: Span,
|
let digits = args.decimals_value;
|
||||||
decimals: bool,
|
let config = &args.config;
|
||||||
digits: Option<i64>,
|
|
||||||
group_digits: bool,
|
|
||||||
config: &Config,
|
|
||||||
) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
let res = if group_digits {
|
let decimal_value = digits.unwrap_or(0) as usize;
|
||||||
format_int(*val) // int.to_formatted_string(*locale)
|
let res = format_int(*val, false, decimal_value);
|
||||||
} else {
|
|
||||||
val.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::String { val: res, span }
|
Value::String { val: res, span }
|
||||||
}
|
}
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
@ -247,6 +244,15 @@ pub fn action(
|
|||||||
val: input.into_string(", ", config),
|
val: input.into_string(", ", config),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Error { error } => Value::String {
|
||||||
|
val: {
|
||||||
|
match into_code(error) {
|
||||||
|
Some(code) => code,
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
},
|
||||||
Value::Nothing { .. } => Value::String {
|
Value::Nothing { .. } => Value::String {
|
||||||
val: "".to_string(),
|
val: "".to_string(),
|
||||||
span,
|
span,
|
||||||
@ -279,21 +285,29 @@ pub fn action(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn format_int(int: i64) -> String {
|
|
||||||
int.to_string()
|
|
||||||
|
|
||||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
|
||||||
// #[cfg(windows)]
|
let locale = get_system_locale();
|
||||||
// {
|
|
||||||
// int.to_formatted_string(&Locale::en)
|
let str = if group_digits {
|
||||||
// }
|
int.to_formatted_string(&locale)
|
||||||
// #[cfg(not(windows))]
|
} else {
|
||||||
// {
|
int.to_string()
|
||||||
// match SystemLocale::default() {
|
};
|
||||||
// Ok(locale) => int.to_formatted_string(&locale),
|
|
||||||
// Err(_) => int.to_formatted_string(&Locale::en),
|
if decimals > 0 {
|
||||||
// }
|
let decimal_point = locale.decimal();
|
||||||
// }
|
|
||||||
|
format!(
|
||||||
|
"{}{decimal_point}{dummy:0<decimals$}",
|
||||||
|
str,
|
||||||
|
decimal_point = decimal_point,
|
||||||
|
dummy = "",
|
||||||
|
decimals = decimals
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
str
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -27,13 +27,17 @@ impl Command for Alias {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["abbr", "aka", "fn", "func", "function"]
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
@ -45,10 +49,17 @@ impl Command for Alias {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Alias ll to ls -l",
|
Example {
|
||||||
example: "alias ll = ls -l",
|
description: "Alias ll to ls -l",
|
||||||
result: None,
|
example: "alias ll = ls -l",
|
||||||
}]
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Make an alias that makes a list of all custom commands",
|
||||||
|
example: "alias customs = ($nu.scope.commands | where is_custom | get command)",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
crates/nu-command/src/core_commands/ast.rs
Normal file
77
crates/nu-command/src/core_commands/ast.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Ast;
|
||||||
|
|
||||||
|
impl Command for Ast {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ast"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Print the abstract syntax tree (ast) for a pipeline."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ast")
|
||||||
|
.required(
|
||||||
|
"pipeline",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the pipeline to print the ast for",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
|
||||||
|
eprintln!("output: {:#?}\nerror: {:#?}", output, err);
|
||||||
|
|
||||||
|
Ok(PipelineData::new(head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a string",
|
||||||
|
example: "ast 'hello'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a pipeline",
|
||||||
|
example: "ast 'ls | where name =~ README'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a pipeline with an error",
|
||||||
|
example: "ast 'for x in 1..10 { echo $x '",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::Ast;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(Ast {})
|
||||||
|
}
|
||||||
|
}
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::ReplOperation;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::IntoPipelineData;
|
||||||
|
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commandline;
|
||||||
|
|
||||||
|
impl Command for Commandline {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("commandline")
|
||||||
|
.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 ops = engine_state
|
||||||
|
.repl_operation_queue
|
||||||
|
.lock()
|
||||||
|
.expect("repl op queue mutex");
|
||||||
|
ops.push_back(if call.has_flag("append") {
|
||||||
|
ReplOperation::Append(cmd.as_string()?)
|
||||||
|
} else if call.has_flag("insert") {
|
||||||
|
ReplOperation::Insert(cmd.as_string()?)
|
||||||
|
} else {
|
||||||
|
ReplOperation::Replace(cmd.as_string()?)
|
||||||
|
});
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
} else if let Some(ref cmd) = *engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex")
|
||||||
|
{
|
||||||
|
Ok(Value::String {
|
||||||
|
val: cmd.clone(),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ impl Command for Def {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -28,7 +28,33 @@ impl Command for DefEnv {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
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 {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use nu_engine::{eval_block, CallExt};
|
use nu_engine::{eval_block, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Do;
|
pub struct Do;
|
||||||
@ -20,9 +23,14 @@ impl Command for Do {
|
|||||||
.required("block", SyntaxShape::Any, "the block to run")
|
.required("block", SyntaxShape::Any, "the block to run")
|
||||||
.switch(
|
.switch(
|
||||||
"ignore-errors",
|
"ignore-errors",
|
||||||
"ignore errors as the block runs",
|
"ignore shell errors as the block runs",
|
||||||
Some('i'),
|
Some('i'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"capture-errors",
|
||||||
|
"capture errors as the block runs and return it",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
@ -37,6 +45,7 @@ impl Command for Do {
|
|||||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||||
let ignore_errors = call.has_flag("ignore-errors");
|
let ignore_errors = call.has_flag("ignore-errors");
|
||||||
|
let capture_errors = call.has_flag("capture-errors");
|
||||||
|
|
||||||
let mut stack = stack.captures_to_stack(&block.captures);
|
let mut stack = stack.captures_to_stack(&block.captures);
|
||||||
let block = engine_state.get_block(block.block_id);
|
let block = engine_state.get_block(block.block_id);
|
||||||
@ -85,7 +94,7 @@ impl Command for Do {
|
|||||||
block,
|
block,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
ignore_errors,
|
ignore_errors || capture_errors,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ignore_errors {
|
if ignore_errors {
|
||||||
@ -93,6 +102,77 @@ impl Command for Do {
|
|||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
Err(_) => Ok(PipelineData::new(call.head)),
|
Err(_) => Ok(PipelineData::new(call.head)),
|
||||||
}
|
}
|
||||||
|
} else if capture_errors {
|
||||||
|
// collect stdout and stderr and check exit code.
|
||||||
|
// if exit code is not 0, return back ShellError.
|
||||||
|
match result {
|
||||||
|
Ok(PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
}) => {
|
||||||
|
// collect all output first.
|
||||||
|
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 mut stdout_ctrlc = None;
|
||||||
|
let stdout_msg = match stdout {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(stdout_stream) => {
|
||||||
|
stdout_ctrlc = stdout_stream.ctrlc.clone();
|
||||||
|
stdout_stream.into_string().map(|s| s.item)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut exit_code_ctrlc = None;
|
||||||
|
let exit_code: Vec<Value> = match exit_code {
|
||||||
|
None => vec![],
|
||||||
|
Some(exit_code_stream) => {
|
||||||
|
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||||
|
exit_code_stream.into_iter().collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||||
|
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||||
|
if *code != 0 {
|
||||||
|
return Err(ShellError::ExternalCommand(
|
||||||
|
"External command runs to failed".to_string(),
|
||||||
|
stderr_msg,
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// construct pipeline data to our caller
|
||||||
|
Ok(PipelineData::ExternalStream {
|
||||||
|
stdout: Some(RawStream::new(
|
||||||
|
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
|
||||||
|
stdout_ctrlc,
|
||||||
|
span,
|
||||||
|
)),
|
||||||
|
stderr: Some(RawStream::new(
|
||||||
|
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||||
|
stderr_ctrlc,
|
||||||
|
span,
|
||||||
|
)),
|
||||||
|
exit_code: Some(ListStream::from_stream(
|
||||||
|
exit_code.into_iter(),
|
||||||
|
exit_code_ctrlc,
|
||||||
|
)),
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(other) => Ok(other),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -106,10 +186,15 @@ impl Command for Do {
|
|||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::test_string("hello")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block and ignore errors",
|
description: "Run the block and ignore shell errors",
|
||||||
example: r#"do -i { thisisnotarealcommand }"#,
|
example: r#"do -i { thisisnotarealcommand }"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Abort the pipeline if a program returns a non-zero exit code",
|
||||||
|
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block, with a positional parameter",
|
description: "Run the block, with a positional parameter",
|
||||||
example: r#"do {|x| 100 + $x } 50"#,
|
example: r#"do {|x| 100 + $x } 50"#,
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,7 +14,7 @@ impl Command for Echo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Echo the arguments back to the user."
|
"Returns its arguments, ignoring the piped-in value."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -23,6 +23,12 @@ impl Command for Echo {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"When given no arguments, it returns an empty string. When given one argument,
|
||||||
|
it returns it. Otherwise, it returns a list of the arguments. There is usually
|
||||||
|
little reason to use this over just writing the values as-is."#
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -57,13 +63,17 @@ impl Command for Echo {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Put a hello message in the pipeline",
|
description: "Put a list of numbers in the pipeline. This is the same as [1 2 3].",
|
||||||
example: "echo 'hello'",
|
example: "echo 1 2 3",
|
||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Print the value of the special '$nu' variable",
|
description:
|
||||||
example: "echo $nu",
|
"Returns the piped-in value, by using the special $in variable to obtain it.",
|
||||||
|
example: "echo $in",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -2,8 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
Value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,7 +15,12 @@ impl Command for ErrorMake {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("error make")
|
Signature::build("error make")
|
||||||
.optional("error_struct", SyntaxShape::Record, "the error to create")
|
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||||
|
.switch(
|
||||||
|
"unspanned",
|
||||||
|
"remove the origin label from the error",
|
||||||
|
Some('u'),
|
||||||
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +29,7 @@ impl Command for ErrorMake {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["err", "panic", "crash", "throw"]
|
vec!["panic", "crash", "throw"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -33,42 +37,32 @@ impl Command for ErrorMake {
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||||
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
|
let unspanned = call.has_flag("unspanned");
|
||||||
|
|
||||||
if let Some(arg) = arg {
|
if unspanned {
|
||||||
Ok(make_error(&arg, span)
|
Err(make_error(&arg, None).unwrap_or_else(|| {
|
||||||
.map(|err| Value::Error { error: err })
|
ShellError::GenericError(
|
||||||
.unwrap_or_else(|| Value::Error {
|
"Creating error value not supported.".into(),
|
||||||
error: ShellError::GenericError(
|
"unsupported error format".into(),
|
||||||
"Creating error value not supported.".into(),
|
Some(span),
|
||||||
"unsupported error format".into(),
|
None,
|
||||||
Some(span),
|
Vec::new(),
|
||||||
None,
|
)
|
||||||
Vec::new(),
|
}))
|
||||||
),
|
|
||||||
})
|
|
||||||
.into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
input.map(
|
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
|
||||||
move |value| {
|
ShellError::GenericError(
|
||||||
make_error(&value, span)
|
"Creating error value not supported.".into(),
|
||||||
.map(|err| Value::Error { error: err })
|
"unsupported error format".into(),
|
||||||
.unwrap_or_else(|| Value::Error {
|
Some(span),
|
||||||
error: ShellError::GenericError(
|
None,
|
||||||
"Creating error value not supported.".into(),
|
Vec::new(),
|
||||||
"unsupported error format".into(),
|
)
|
||||||
Some(span),
|
}))
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
ctrlc,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +87,7 @@ impl Command for ErrorMake {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
||||||
if let Value::Record { .. } = &value {
|
if let Value::Record { .. } = &value {
|
||||||
let msg = value.get_data_by_key("msg");
|
let msg = value.get_data_by_key("msg");
|
||||||
let label = value.get_data_by_key("label");
|
let label = value.get_data_by_key("label");
|
||||||
@ -130,7 +124,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
|||||||
) => Some(ShellError::GenericError(
|
) => Some(ShellError::GenericError(
|
||||||
message,
|
message,
|
||||||
label_text,
|
label_text,
|
||||||
Some(throw_span),
|
throw_span,
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
@ -140,7 +134,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
|||||||
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
||||||
message,
|
message,
|
||||||
"originates from here".to_string(),
|
"originates from here".to_string(),
|
||||||
Some(throw_span),
|
throw_span,
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
|
@ -18,12 +18,12 @@ impl Command for ExportCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Export custom commands or environment variables from a module."
|
"Export definitions or environment variables from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -59,4 +59,8 @@ impl Command for ExportCommand {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ impl Command for ExportAlias {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -51,4 +51,8 @@ impl Command for ExportAlias {
|
|||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["aka", "abbr", "module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl Command for ExportDef {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -55,4 +55,8 @@ impl Command for ExportDef {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,33 @@ impl Command for ExportDefEnv {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
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:
|
||||||
|
|
||||||
|
export 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:
|
||||||
|
|
||||||
|
export 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 {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -55,4 +81,8 @@ impl Command for ExportDefEnv {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ impl Command for ExportExtern {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -47,4 +47,8 @@ impl Command for ExportExtern {
|
|||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["signature", "module", "declare"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user