forked from extern/nushell
Compare commits
488 Commits
Author | SHA1 | Date | |
---|---|---|---|
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]
|
||||
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
|
||||
|
||||
|
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 |
|
||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
||||
name: Feature Request
|
||||
description: "When you want a new feature for something that doesn't already exist"
|
||||
labels: "enhancement"
|
||||
body:
|
||||
- type: textarea
|
||||
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
|
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@ -4,6 +4,12 @@
|
||||
|
||||
# Tests
|
||||
|
||||
Make sure you've done the following:
|
||||
|
||||
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
|
||||
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
|
||||
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
|
||||
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
|
||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
|
51
.github/workflows/ci.yml
vendored
51
.github/workflows/ci.yml
vendored
@ -23,12 +23,13 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
# components: rustfmt, clippy
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
@ -74,11 +75,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
@ -109,11 +111,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
@ -133,10 +136,7 @@ jobs:
|
||||
- run: python -m pip install tox
|
||||
|
||||
- name: Install virtualenv
|
||||
run: |
|
||||
git clone https://github.com/kubouch/virtualenv.git && \
|
||||
cd virtualenv && \
|
||||
git checkout engine-q-update
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
@ -162,11 +162,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
|
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 }}
|
18
.github/workflows/release-pkg.nu
vendored
18
.github/workflows/release-pkg.nu
vendored
@ -41,7 +41,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
} else {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
sudo apt install musl-tools -y
|
||||
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
# Build for Windows without static-link-openssl feature
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in ['windows-latest'] {
|
||||
if ($flags | str trim | empty?) {
|
||||
if ($flags | str trim | is-empty) {
|
||||
cargo build --release --all --target $target --features=extra
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra $flags
|
||||
@ -80,7 +80,7 @@ let ver = if $os == 'windows-latest' {
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
}
|
||||
if ($ver | str trim | empty?) {
|
||||
if ($ver | str trim | is-empty) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { $ver }
|
||||
|
||||
@ -102,8 +102,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
let releaseStem = $'($bin)-($version)-($target)'
|
||||
|
||||
$'(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'
|
||||
curl https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o $'($dist)\LICENSE-for-less.txt'
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
# Create Windows msi release package
|
||||
if (get-env _EXTRA_) == 'msi' {
|
||||
@ -113,7 +113,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
cd $src; hr-line
|
||||
# Wix need the binaries be stored in target/release/
|
||||
cp -r $'($dist)/*' target/release/
|
||||
cargo install cargo-wix --version 0.3.2
|
||||
cargo install cargo-wix --version 0.3.3
|
||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||
echo $'::set-output name=archive::($wixRelease)'
|
||||
|
||||
@ -124,14 +124,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
7z a $archive *
|
||||
print $'archive: ---> ($archive)';
|
||||
let pkg = (ls -f $archive | get name)
|
||||
if not ($pkg | empty?) {
|
||||
if not ($pkg | is-empty) {
|
||||
echo $'::set-output name=archive::($pkg | get 0)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||
@ -143,7 +143,7 @@ def 'hr-line' [
|
||||
--blank-line(-b): bool
|
||||
] {
|
||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||
if $blank-line { char nl }
|
||||
if $blank_line { char nl }
|
||||
}
|
||||
|
||||
# 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 }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v1
|
||||
uses: hustcer/setup-nu@v2.1
|
||||
with:
|
||||
version: 0.63.0
|
||||
version: 0.68.0
|
||||
env:
|
||||
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:
|
||||
workflow_dispatch:
|
||||
@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
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
|
||||
$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 }}
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -29,3 +29,9 @@ debian/nu/
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
*.rsproj
|
||||
*.rsproj.user
|
||||
*.sln
|
@ -1,21 +1,14 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
||||
|
||||
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.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
Welcome to Nushell!
|
||||
|
||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||
<!--WIP-->
|
||||
|
||||
## 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
|
||||
git clone https://github.com/nushell/nushell
|
||||
@ -28,24 +21,24 @@ cargo build
|
||||
- Build and run Nushell:
|
||||
|
||||
```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
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
cargo run --features=extra
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
cargo test --workspace --features=extra
|
||||
```
|
||||
|
||||
- Run all tests for a specific command
|
||||
@ -71,5 +64,11 @@ cargo build
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```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"
|
||||
```
|
||||
|
1754
Cargo.lock
generated
1754
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
54
Cargo.toml
54
Cargo.toml
@ -11,7 +11,7 @@ name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.63.1"
|
||||
version = "0.69.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -28,47 +28,51 @@ members = [
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.19"
|
||||
crossterm = "0.23.0"
|
||||
chrono = { version = "0.4.21", features = ["serde"] }
|
||||
crossterm = "0.24.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "4.5.0"
|
||||
nu-ansi-term = "0.45.1"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.63.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.63.1" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.63.1" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.63.1" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.63.1" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.63.1" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.63.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.63.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.63.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.63.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.63.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.63.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.63.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.63.1" }
|
||||
pretty_env_logger = "0.4.0"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.69.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.1" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.69.1" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.69.1" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.69.1" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.69.1" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.69.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.69.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.69.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.69.1" }
|
||||
reedline = { version = "0.12.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
rayon = "1.5.1"
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
||||
is_executable = "1.0.1"
|
||||
simplelog = "0.12.0"
|
||||
time = "0.3.12"
|
||||
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
# Our dependencies don't use OpenSSL on Windows
|
||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.63.1" }
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.69.1" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.5.1"
|
||||
serial_test = "0.8.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.12.0"
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
itertools = "0.10.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
@ -76,9 +80,9 @@ winres = "0.1"
|
||||
|
||||
[features]
|
||||
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"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
wasi = []
|
||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
|
249
README.md
249
README.md
@ -1,5 +1,4 @@
|
||||
# README
|
||||
|
||||
# Nushell <!-- omit in toc -->
|
||||
[](https://crates.io/crates/nu)
|
||||

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

|
||||

|
||||
|
||||
## Nushell
|
||||
|
||||
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
|
||||
|
||||
This project has reached a minimum-viable product level of quality.
|
||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
||||
Future releases will work to fill out missing features and improve stability.
|
||||
Its design is also subject to change as it matures.
|
||||
This project has reached a minimum-viable-product level of quality. Many people use it as their daily driver, but it may be unstable for some commands. Nu's design is subject to change as it matures.
|
||||
|
||||
Nu comes with a set of built-in commands (listed below).
|
||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
||||
## Learning About Nu
|
||||
|
||||
## Learning more
|
||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
There are a few good resources to learn about Nu.
|
||||
There is a [book](https://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/).
|
||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||
|
||||
## 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.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.60 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
- pkg-config and libssl (only needed on Linux)
|
||||
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
- To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
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
|
||||
```bash
|
||||
# Linux and macOS
|
||||
brew install nushell
|
||||
# Windows
|
||||
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
|
||||
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
|
||||
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
|
||||
#### Fedora
|
||||
|
||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
||||
|
||||
## Philosophy
|
||||
|
||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
||||
Rather than thinking of files and data as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory what you get back is a table of rows, where each row represents an item in that directory.
|
||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
|
||||
### Pipelines
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||
Just as the Unix philosophy, Nu allows commands to output 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).
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `autoview`)
|
||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
```shell
|
||||
> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬───────┬──────────────
|
||||
# │ name │ type │ size │ modified
|
||||
───┼────────┼──────┼───────┼──────────────
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
||||
3 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||
4 │ images │ Dir │ 160 B │ 5 months ago
|
||||
5 │ src │ Dir │ 128 B │ 1 day ago
|
||||
6 │ target │ Dir │ 160 B │ 5 days ago
|
||||
7 │ tests │ Dir │ 192 B │ 3 months ago
|
||||
───┴────────┴──────┴───────┴──────────────
|
||||
> ls | where type == "dir" | table
|
||||
╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||
│ # │ name │ type │ size │ modified │
|
||||
├────┼──────────┼──────┼─────────┼───────────────┤
|
||||
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 9 │ target │ dir │ 0 B │ a day ago │
|
||||
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||
╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||
```
|
||||
|
||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
||||
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:
|
||||
|
||||
```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.
|
||||
@ -137,15 +108,13 @@ For example, we could use the built-in `ps` command to get a list of the running
|
||||
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
||||
0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
|
||||
1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
|
||||
2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
|
||||
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
|
||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
||||
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||
│ # │ pid │ name │ cpu │ mem │ virtual │
|
||||
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||
```
|
||||
|
||||
### Opening files
|
||||
@ -155,72 +124,49 @@ For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml
|
||||
────────────────────┬───────────────────────────
|
||||
bin │ [table 18 rows]
|
||||
build-dependencies │ [row serde toml]
|
||||
dependencies │ [row 29 columns]
|
||||
dev-dependencies │ [row nu-test-support]
|
||||
features │ [row 19 columns]
|
||||
package │ [row 12 columns]
|
||||
workspace │ [row members]
|
||||
────────────────────┴───────────────────────────
|
||||
╭──────────────────┬────────────────────╮
|
||||
│ bin │ [table 1 row] │
|
||||
│ dependencies │ {record 24 fields} │
|
||||
│ dev-dependencies │ {record 8 fields} │
|
||||
│ features │ {record 10 fields} │
|
||||
│ package │ {record 13 fields} │
|
||||
│ profile │ {record 3 fields} │
|
||||
│ target │ {record 2 fields} │
|
||||
│ workspace │ {record 1 field} │
|
||||
╰──────────────────┴────────────────────╯
|
||||
```
|
||||
|
||||
We can pipeline this into a command that gets the contents of one of the columns:
|
||||
We can pipe this into a command that gets the contents of one of the columns:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package
|
||||
───────────────┬────────────────────────────────────
|
||||
authors │ [table 1 rows]
|
||||
default-run │ nu
|
||||
description │ A new type of shell
|
||||
documentation │ https://www.nushell.sh/book/
|
||||
edition │ 2018
|
||||
exclude │ [table 1 rows]
|
||||
homepage │ https://www.nushell.sh
|
||||
license │ MIT
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.32.0
|
||||
───────────────┴────────────────────────────────────
|
||||
╭───────────────┬────────────────────────────────────╮
|
||||
│ authors │ [list 1 item] │
|
||||
│ default-run │ nu │
|
||||
│ description │ A new type of shell │
|
||||
│ documentation │ https://www.nushell.sh/book/ │
|
||||
│ edition │ 2018 │
|
||||
│ exclude │ [list 1 item] │
|
||||
│ homepage │ https://www.nushell.sh │
|
||||
│ license │ MIT │
|
||||
│ name │ nu │
|
||||
│ readme │ README.md │
|
||||
│ repository │ https://github.com/nushell/nushell │
|
||||
│ rust-version │ 1.60 │
|
||||
│ version │ 0.63.1 │
|
||||
╰───────────────┴────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
And if needed we can drill down further:
|
||||
|
||||
```shell
|
||||
> 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
|
||||
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
||||
This allows you to extend nu for your needs.
|
||||
|
||||
There are a few examples in the `plugins` directory.
|
||||
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.
|
||||
|
||||
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.
|
||||
@ -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.
|
||||
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
|
||||
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 |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
@ -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.
|
||||
|
||||
### Integrations
|
||||
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
||||
- [starship](https://github.com/starship/starship)
|
||||
- [oh-my-posh](https://ohmyposh.dev)
|
||||
- [Couchbase Shell](https://couchbase.sh)
|
||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||
### Mentions
|
||||
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details.
|
||||
|
||||
Thanks to all the people who already contributed!
|
||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
||||
|
@ -1,3 +1,3 @@
|
||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||
|
||||
> register -e json ./nu_plugin_query
|
||||
> 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_inc'
|
||||
'nu_plugin_query'
|
||||
'nu_plugin_custom_values'
|
||||
)
|
||||
|
||||
echo "Building nushell"
|
||||
|
@ -24,9 +24,13 @@ cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_custom_values
|
||||
echo Building nu_plugin_custom_values.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
@ -11,6 +11,7 @@ let plugins = [
|
||||
nu_plugin_gstat,
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
|
@ -1,31 +1,40 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "CLI-related functionality for Nushell"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.63.1"
|
||||
version = "0.69.1"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.63.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.63.1" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.69.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.69.1" }
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.63.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.63.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.63.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.63.1" }
|
||||
nu-ansi-term = "0.45.1"
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.63.1" }
|
||||
crossterm = "0.23.0"
|
||||
miette = { version = "4.5.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.69.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.69.1" }
|
||||
reedline = { version = "0.12.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
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"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
percent-encoding = "2"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.26.2"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -5,21 +5,27 @@ use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
/// Run a command (or commands) given to us by the user
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
init_cwd: &Path,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
table_mode: Option<Value>,
|
||||
) -> Result<()> {
|
||||
// Run a command (or commands) given to us by the user
|
||||
) -> Result<Option<i64>> {
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Parse the source code
|
||||
let (block, delta) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
@ -39,43 +45,19 @@ pub fn evaluate_commands(
|
||||
(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);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
let mut config = engine_state.get_config().clone();
|
||||
if let Some(t_mode) = table_mode {
|
||||
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) {
|
||||
// Run the block
|
||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
if let Some(t_mode) = table_mode {
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
}
|
||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||
}
|
||||
Err(err) => {
|
||||
@ -84,11 +66,11 @@ pub fn evaluate_commands(
|
||||
report_error(&working_set, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
}
|
||||
|
||||
impl CommandCompletion {
|
||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
||||
_: &StateWorkingSet,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
flattened,
|
||||
flat_shape,
|
||||
force_completion_after_space,
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,19 +46,21 @@ impl CommandCompletion {
|
||||
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
) && matches!(
|
||||
item.path()
|
||||
.file_name()
|
||||
.map(|x| match_algorithm
|
||||
if self.engine_state.config.max_external_completion_results
|
||||
> executables.len() as i64
|
||||
&& !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
&& matches!(
|
||||
item.path().file_name().map(|x| match_algorithm
|
||||
.matches_str(&x.to_string_lossy(), prefix)),
|
||||
Some(true)
|
||||
) && is_executable::is_executable(&item.path())
|
||||
Some(true)
|
||||
)
|
||||
&& is_executable::is_executable(&item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
@ -114,7 +119,8 @@ impl CommandCompletion {
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
let results = if find_externals {
|
||||
|
||||
if find_externals {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
@ -146,9 +152,7 @@ impl CommandCompletion {
|
||||
results
|
||||
} else {
|
||||
results
|
||||
};
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,12 +203,23 @@ impl Completer for CommandCompletion {
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| ((span.end - span.start) == 0)
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
self.complete_commands(working_set, span, offset, 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 {
|
||||
vec![]
|
||||
};
|
||||
|
@ -2,10 +2,11 @@ use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||
};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span,
|
||||
BlockId, PipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
@ -56,64 +57,121 @@ impl NuCompleter {
|
||||
suggestions
|
||||
}
|
||||
|
||||
fn external_completion(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
spans: Vec<String>,
|
||||
offset: usize,
|
||||
span: Span,
|
||||
) -> 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
|
||||
.into_iter()
|
||||
.map(|it| Value::String {
|
||||
val: it,
|
||||
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 result;
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed to eval completer block: {}", err),
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||
let initial_line = line.to_string();
|
||||
let mut line = line.to_string();
|
||||
line.insert(pos, 'a');
|
||||
new_line.push(b'a');
|
||||
let pos = offset + pos;
|
||||
let (output, _err) = parse(
|
||||
&mut working_set,
|
||||
Some("completer"),
|
||||
line.as_bytes(),
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
let mut spans: Vec<String> = vec![];
|
||||
|
||||
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
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
|
||||
// Create a new span
|
||||
let new_span = Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1,
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
} else {
|
||||
Span {
|
||||
start: flat.0.start - span_offset,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - flat.0.start);
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
@ -135,8 +193,38 @@ impl NuCompleter {
|
||||
|
||||
// Flags completion
|
||||
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 {
|
||||
return self.external_completion(block_id, spans, offset, new_span);
|
||||
}
|
||||
}
|
||||
|
||||
// specially check if it is currently empty - always complete commands
|
||||
if flat_idx == 0 && working_set.get_span_contents(new_span).is_empty() {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
&working_set,
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
FlatShape::String,
|
||||
true,
|
||||
);
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
@ -147,6 +235,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 &flat.1 {
|
||||
FlatShape::Custom(decl_id) => {
|
||||
@ -179,6 +303,18 @@ impl NuCompleter {
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
flat_shape => {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
@ -186,9 +322,10 @@ impl NuCompleter {
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
flat_shape.clone(),
|
||||
false,
|
||||
);
|
||||
|
||||
let out: Vec<_> = self.process_completion(
|
||||
let mut out: Vec<_> = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
@ -197,21 +334,30 @@ impl NuCompleter {
|
||||
pos,
|
||||
);
|
||||
|
||||
if out.is_empty() {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
|
||||
return out;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Try to complete using an exnternal compelter (if set)
|
||||
if let Some(block_id) = config.external_completer {
|
||||
return self
|
||||
.external_completion(block_id, spans, offset, new_span);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -219,7 +365,7 @@ impl NuCompleter {
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +375,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)
|
||||
// and the depth (a.b.c)
|
||||
fn most_left_variable(
|
||||
@ -278,3 +503,65 @@ fn most_left_variable(
|
||||
|
||||
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 std::sync::Arc;
|
||||
|
||||
use super::completer::map_value_completions;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
||||
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 {
|
||||
@ -144,7 +83,7 @@ impl Completer for CustomCompletion {
|
||||
.and_then(|val| {
|
||||
val.as_list()
|
||||
.ok()
|
||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
||||
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
@ -189,7 +128,7 @@ impl Completer for CustomCompletion {
|
||||
|
||||
completions
|
||||
}
|
||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
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)
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
|
@ -1,23 +1,33 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
||||
use nu_protocol::{PipelineData, Span};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_parser::ParseError;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
const PLUGIN_FILE: &str = "plugin.nu";
|
||||
|
||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
// Reading signatures from signature file
|
||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, storage_path);
|
||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||
|
||||
let plugin_path = engine_state.plugin_signatures.clone();
|
||||
if let Some(plugin_path) = plugin_path {
|
||||
@ -40,8 +50,23 @@ pub fn read_plugin_file(
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
pub fn add_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
if let Some(plugin_file) = plugin_file {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
match canonicalize_with(&plugin_file.item, cwd) {
|
||||
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||
Err(_) => {
|
||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
@ -66,12 +91,10 @@ pub fn eval_config_contents(
|
||||
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) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) =
|
||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
||||
{
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
@ -84,3 +107,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 nu_engine::convert_env_values;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::Type;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
@ -34,7 +35,7 @@ pub fn evaluate_file(
|
||||
|
||||
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(" "));
|
||||
|
||||
if !eval_source(
|
||||
@ -65,7 +66,7 @@ pub fn print_table_or_error(
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
config: &mut Config,
|
||||
) {
|
||||
) -> Option<i64> {
|
||||
let exit_code = match &mut pipeline_data {
|
||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||
_ => None,
|
||||
@ -76,59 +77,63 @@ pub fn print_table_or_error(
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
Some(decl_id) => {
|
||||
let table = engine_state.get_decl(decl_id).run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
} else {
|
||||
let table = command.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
for item in table {
|
||||
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));
|
||||
match table {
|
||||
Ok(table) => {
|
||||
print_or_exit(table, engine_state, config);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
Err(error) => {
|
||||
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 => {
|
||||
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));
|
||||
}
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
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 prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use repl::{eval_env_change_hook, eval_hook};
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -1,8 +1,8 @@
|
||||
use {
|
||||
nu_ansi_term::{ansi::RESET, Style},
|
||||
reedline::{
|
||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion,
|
||||
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion, UndoBehavior,
|
||||
},
|
||||
};
|
||||
|
||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
||||
fn can_partially_complete(
|
||||
&mut self,
|
||||
_values_updated: bool,
|
||||
_line_buffer: &mut LineBuffer,
|
||||
_editor: &mut Editor,
|
||||
_completer: &mut dyn Completer,
|
||||
) -> bool {
|
||||
false
|
||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// 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 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() {
|
||||
self.reset_position();
|
||||
self.values = completer.complete(input, start);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
||||
self.values =
|
||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
||||
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||
self.values = completer.complete(
|
||||
trimmed_buffer.as_str(),
|
||||
editor.line_buffer().insertion_point(),
|
||||
);
|
||||
self.reset_position();
|
||||
}
|
||||
}
|
||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
||||
/// collected from the completer
|
||||
fn update_working_details(
|
||||
&mut self,
|
||||
line_buffer: &mut LineBuffer,
|
||||
editor: &mut Editor,
|
||||
completer: &mut dyn Completer,
|
||||
painter: &Painter,
|
||||
) {
|
||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
||||
match event {
|
||||
MenuEvent::Activate(_) => {
|
||||
self.reset_position();
|
||||
self.input = Some(line_buffer.get_buffer().to_string());
|
||||
self.update_values(line_buffer, completer);
|
||||
self.input = Some(editor.get_buffer().to_string());
|
||||
self.update_values(editor, completer);
|
||||
}
|
||||
MenuEvent::Deactivate => self.active = false,
|
||||
MenuEvent::Edit(_) => {
|
||||
self.reset_position();
|
||||
self.update_values(line_buffer, completer);
|
||||
self.update_values(editor, completer);
|
||||
self.update_examples()
|
||||
}
|
||||
MenuEvent::NextElement => {
|
||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
let start = span.start.min(line_buffer.len());
|
||||
let end = span.end.min(line_buffer.len());
|
||||
let start = span.start.min(editor.line_buffer().len());
|
||||
let end = span.end.min(editor.line_buffer().len());
|
||||
|
||||
let string_len = if let Some(example_index) = self.example_index {
|
||||
let example = self
|
||||
.examples
|
||||
let replacement = if let Some(example_index) = self.example_index {
|
||||
self.examples
|
||||
.get(example_index)
|
||||
.expect("the example index is always checked");
|
||||
|
||||
line_buffer.replace(start..end, example);
|
||||
example.len()
|
||||
.expect("the example index is always checked")
|
||||
} else {
|
||||
line_buffer.replace(start..end, &value);
|
||||
value.len()
|
||||
&value
|
||||
};
|
||||
|
||||
let mut offset = line_buffer.insertion_point();
|
||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||
line_buffer.set_insertion_point(offset);
|
||||
editor.edit_buffer(
|
||||
|lb| {
|
||||
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_protocol::{engine::EngineState, levenshtein_distance};
|
||||
use reedline::{Completer, Suggestion};
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||
@ -19,6 +20,10 @@ impl NuHelpCompleter {
|
||||
.filter(|(sig, _, _, _)| {
|
||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig
|
||||
.search_terms
|
||||
.iter()
|
||||
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
||||
|| sig
|
||||
.extra_usage
|
||||
.to_lowercase()
|
||||
@ -49,7 +54,7 @@ impl NuHelpCompleter {
|
||||
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() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
@ -61,27 +66,28 @@ impl NuHelpCompleter {
|
||||
{
|
||||
long_desc.push_str("\r\nParameters:\r\n");
|
||||
for positional in &sig.required_positional {
|
||||
long_desc
|
||||
.push_str(&format!(" {}: {}\r\n", positional.name, positional.desc));
|
||||
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
long_desc.push_str(&format!(
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional) {}: {}\r\n",
|
||||
positional.name, positional.desc
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &sig.rest_positional {
|
||||
long_desc.push_str(&format!(
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" ...{}: {}\r\n",
|
||||
rest_positional.name, rest_positional.desc
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let extra: Vec<String> = examples
|
||||
.iter()
|
||||
.map(|example| example.example.to_string())
|
||||
.map(|example| example.example.replace('\n', "\r\n"))
|
||||
.collect();
|
||||
|
||||
Suggestion {
|
||||
|
@ -19,6 +19,10 @@ impl Command for NuHighlight {
|
||||
"Syntax highlight the input string."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["syntax", "color", "convert"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -17,15 +17,27 @@ impl Command for Print {
|
||||
Signature::build("print")
|
||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||
.switch(
|
||||
"no_newline",
|
||||
"no-newline",
|
||||
"print without inserting a newline for the line ending",
|
||||
Some('n'),
|
||||
)
|
||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"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(
|
||||
@ -36,12 +48,13 @@ impl Command for Print {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
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;
|
||||
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline)?;
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
|
@ -1,5 +1,6 @@
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::DefaultPrompt;
|
||||
|
||||
use {
|
||||
reedline::{
|
||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||
@ -86,6 +87,11 @@ impl NushellPrompt {
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
|
@ -15,6 +15,10 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
|
||||
fn get_prompt_string(
|
||||
prompt: &str,
|
||||
@ -98,6 +102,20 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
match left_prompt_string {
|
||||
Some(prompt_string) => Some(format!(
|
||||
"{}{}{}",
|
||||
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||
)),
|
||||
None => left_prompt_string,
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
};
|
||||
|
||||
let right_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
|
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
@ -814,7 +815,6 @@ fn event_from_record(
|
||||
) -> Result<ReedlineEvent, ShellError> {
|
||||
let event = match name {
|
||||
"none" => ReedlineEvent::None,
|
||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||
@ -875,7 +875,16 @@ fn edit_from_record(
|
||||
"moveleft" => EditCommand::MoveLeft,
|
||||
"moveright" => EditCommand::MoveRight,
|
||||
"movewordleft" => EditCommand::MoveWordLeft,
|
||||
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
||||
"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" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
@ -888,6 +897,7 @@ fn edit_from_record(
|
||||
"insertnewline" => EditCommand::InsertNewline,
|
||||
"backspace" => EditCommand::Backspace,
|
||||
"delete" => EditCommand::Delete,
|
||||
"cutchar" => EditCommand::CutChar,
|
||||
"backspaceword" => EditCommand::BackspaceWord,
|
||||
"deleteword" => EditCommand::DeleteWord,
|
||||
"clear" => EditCommand::Clear,
|
||||
@ -898,7 +908,11 @@ fn edit_from_record(
|
||||
"cuttoend" => EditCommand::CutToEnd,
|
||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||
"cutwordleft" => EditCommand::CutWordLeft,
|
||||
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||
"cutwordright" => EditCommand::CutWordRight,
|
||||
"cutbigwordright" => EditCommand::CutBigWordRight,
|
||||
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
|
||||
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
|
||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||
"uppercaseword" => EditCommand::UppercaseWord,
|
||||
|
@ -2,35 +2,60 @@ use crate::{
|
||||
completions::NuCompleter,
|
||||
prompt_update,
|
||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||
util::{eval_source, report_error},
|
||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use log::{info, trace};
|
||||
use fancy_regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{info, trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::lex;
|
||||
use nu_parser::{lex, parse};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
BlockId, PipelineData, PositionalArg, ShellError, Span, Value,
|
||||
ast::PathMember,
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||
Spanned, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, Vi};
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
sync::atomic::Ordering,
|
||||
time::Instant,
|
||||
};
|
||||
use strip_ansi_escapes::strip;
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
// These first two have been moved to prompt_update to get as close as possible to the prompt.
|
||||
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
|
||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
history_path: Option<PathBuf>,
|
||||
nushell_path: &str,
|
||||
is_perf_true: bool,
|
||||
prerun_command: Option<Spanned<String>>,
|
||||
) -> Result<()> {
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
|
||||
// Guard against invocation without a connected terminal.
|
||||
// reedline / crossterm event polling will fail without a connected tty
|
||||
if !atty::is(atty::Stream::Stdin) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Nushell launched as interactive REPL but STDIN is not a TTY, either launch in a valid terminal or provide arguments to invoke a script!",
|
||||
))
|
||||
.into_diagnostic();
|
||||
}
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
@ -76,28 +101,77 @@ pub fn evaluate_repl(
|
||||
);
|
||||
}
|
||||
|
||||
// Get the config once for the history `max_history_size`
|
||||
// Updating that will not be possible in one session
|
||||
let mut config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
let hist_sesh = match line_editor.get_history_session_id() {
|
||||
Some(id) => i64::from(id),
|
||||
None => 0,
|
||||
};
|
||||
engine_state.history_session_id = hist_sesh;
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
);
|
||||
if let Some(history_path) = history_path.as_deref() {
|
||||
if is_perf_true {
|
||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
let history = Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
config.max_history_size as usize,
|
||||
history_path.to_path_buf(),
|
||||
)
|
||||
.into_diagnostic()?,
|
||||
);
|
||||
|
||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::PlainText => Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
config.max_history_size as usize,
|
||||
history_path.to_path_buf(),
|
||||
)
|
||||
.into_diagnostic()?,
|
||||
),
|
||||
HistoryFileFormat::Sqlite => Box::new(
|
||||
SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?,
|
||||
),
|
||||
};
|
||||
line_editor = line_editor.with_history(history);
|
||||
};
|
||||
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
let show_banner = config.show_banner;
|
||||
let use_ansi = config.use_ansi_coloring;
|
||||
if show_banner {
|
||||
let banner = get_banner(engine_state, stack);
|
||||
if use_ansi {
|
||||
println!("{}", banner);
|
||||
} else {
|
||||
let stripped_string = {
|
||||
if let Ok(bytes) = strip(&banner) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
banner
|
||||
}
|
||||
};
|
||||
|
||||
println!("{}", stripped_string);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.item.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
||||
}
|
||||
|
||||
loop {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
@ -108,12 +182,24 @@ pub fn evaluate_repl(
|
||||
);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
// Reset the SIGQUIT handler
|
||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
||||
sig_quit.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
@ -130,7 +216,6 @@ pub fn evaluate_repl(
|
||||
engine_state: engine_state.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_animation(config.animate_prompt)
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
}))
|
||||
@ -184,7 +269,10 @@ pub fn evaluate_repl(
|
||||
if is_perf_true {
|
||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
line_editor.sync_history().into_diagnostic()?;
|
||||
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
@ -219,63 +307,22 @@ pub fn evaluate_repl(
|
||||
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = &config.hooks.pre_prompt {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
if let Some(hook) = config.hooks.env_change.clone() {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols, vals: blocks, ..
|
||||
} => {
|
||||
for (idx, env_var) in cols.iter().enumerate() {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_var)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let after = stack.get_env_var(engine_state, env_var).unwrap_or_default();
|
||||
if before != after {
|
||||
if let Err(err) = run_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![before, after.clone()],
|
||||
&blocks[idx],
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_var.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::TypeMismatch(
|
||||
"record for 'env_change' hook".to_string(),
|
||||
x.span().unwrap_or_else(|_| Span::new(0, 0)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
|
||||
if config.shell_integration {
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
let prompt =
|
||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
||||
|
||||
@ -291,37 +338,42 @@ pub fn evaluate_repl(
|
||||
}
|
||||
|
||||
let input = line_editor.read_line(prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = sys.host_name();
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = hostname.clone();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
.replace(line_editor.current_buffer_contents().to_string());
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = &config.hooks.pre_execution {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
if config.shell_integration {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
run_ansi_sequence(PRE_PROMPT_MARKER)?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
@ -332,13 +384,7 @@ pub fn evaluate_repl(
|
||||
|
||||
let orig = s.clone();
|
||||
|
||||
if (orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\'))
|
||||
&& path.is_dir()
|
||||
&& tokens.0.len() == 1
|
||||
{
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
let (path, span) = {
|
||||
if !path.exists() {
|
||||
@ -354,6 +400,14 @@ pub fn evaluate_repl(
|
||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||
};
|
||||
|
||||
stack.add_env_var(
|
||||
"OLDPWD".into(),
|
||||
Value::String {
|
||||
val: cwd.clone(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var(
|
||||
@ -381,9 +435,23 @@ pub fn evaluate_repl(
|
||||
0
|
||||
};
|
||||
|
||||
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
||||
let last_shell = if let Some(v) = last_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::String { val: path, span };
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::Int {
|
||||
val: last_shell as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
@ -395,28 +463,94 @@ pub fn evaluate_repl(
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
}
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: format!("{}", start_time.elapsed().as_millis()),
|
||||
val: format!("{}", cmd_duration.as_millis()),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
// FIXME: permanent state changes like this hopefully in time can be removed
|
||||
// and be replaced by just passing the cwd in where needed
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
let _ = std::env::set_current_dir(path);
|
||||
engine_state.add_env_var("PWD".into(), cwd);
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
c.exit_status = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]7;file://{}{}{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||
percent_encoding::CONTROLS
|
||||
),
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&path,
|
||||
percent_encoding::CONTROLS
|
||||
)
|
||||
))?;
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
}
|
||||
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
while let Some(op) = ops.pop_front() {
|
||||
match op {
|
||||
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
||||
EditCommand::MoveToEnd,
|
||||
EditCommand::InsertString(s),
|
||||
]),
|
||||
ReplOperation::Insert(s) => {
|
||||
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
||||
}
|
||||
ReplOperation::Replace(s) => line_editor
|
||||
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
@ -424,6 +558,13 @@ pub fn evaluate_repl(
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
println!("Error: {:?}", err);
|
||||
// TODO: Identify possible error cases where a hard failure is preferable
|
||||
// Ignoring and reporting could hide bigger problems
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -432,6 +573,390 @@ pub fn evaluate_repl(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
let age = match eval_string_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
None,
|
||||
"(date now) - ('05/10/2019' | into datetime)",
|
||||
) {
|
||||
Ok(Value::Duration { val, .. }) => format_duration(val),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
let banner = format!(
|
||||
r#"{} __ ,
|
||||
{} .--()°'.' {}Welcome to {}Nushell{},
|
||||
{}'|, . ,' {}based on the {}nu{} language,
|
||||
{} !_-(_\ {}where all data is structured!
|
||||
|
||||
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
|
||||
Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
||||
Our {}Documentation{} is located at {}http://nushell.sh{}
|
||||
{}Tweet{} us at {}@nu_shell{}
|
||||
|
||||
{}Nushell{} has been around for:
|
||||
{}
|
||||
|
||||
{}You can disable this banner using the {}config nu{}{} command
|
||||
to modify the config.nu file and setting show_banner to false.
|
||||
|
||||
let-env config {{
|
||||
show_banner: false
|
||||
...
|
||||
}}{}
|
||||
"#,
|
||||
"\x1b[32m", //start line 1 green
|
||||
"\x1b[32m", //start line 2
|
||||
"\x1b[0m", //before welcome
|
||||
"\x1b[32m", //before nushell
|
||||
"\x1b[0m", //after nushell
|
||||
"\x1b[32m", //start line 3
|
||||
"\x1b[0m", //before based
|
||||
"\x1b[32m", //before nu
|
||||
"\x1b[0m", //after nu
|
||||
"\x1b[32m", //start line 4
|
||||
"\x1b[0m", //before where
|
||||
"\x1b[35m", //before Discord purple
|
||||
"\x1b[0m", //after Discord
|
||||
"\x1b[35m", //before Discord URL
|
||||
"\x1b[0m", //after Discord URL
|
||||
"\x1b[1;32m", //before GitHub green_bold
|
||||
"\x1b[0m", //after GitHub
|
||||
"\x1b[1;32m", //before GitHub URL
|
||||
"\x1b[0m", //after GitHub URL
|
||||
"\x1b[32m", //before Documentation
|
||||
"\x1b[0m", //after Documentation
|
||||
"\x1b[32m", //before Documentation URL
|
||||
"\x1b[0m", //after Documentation URL
|
||||
"\x1b[36m", //before Tweet blue
|
||||
"\x1b[0m", //after Tweet
|
||||
"\x1b[1;36m", //before @nu_shell cyan_bold
|
||||
"\x1b[0m", //after @nu_shell
|
||||
"\x1b[32m", //before Nushell
|
||||
"\x1b[0m", //after Nushell
|
||||
age,
|
||||
"\x1b[2;37m", //before banner disable dim white
|
||||
"\x1b[2;36m", //before config nu dim cyan
|
||||
"\x1b[0m", //after config nu
|
||||
"\x1b[2;37m", //after config nu dim white
|
||||
"\x1b[0m", //after banner disable
|
||||
);
|
||||
|
||||
banner
|
||||
}
|
||||
|
||||
// Taken from Nana's simple_eval
|
||||
/// Evaluate a block of Nu code, optionally with input.
|
||||
/// For example, source="$in * 2" will multiply the value in input by 2.
|
||||
pub fn eval_string_with_input(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: Option<Value>,
|
||||
source: &str,
|
||||
) -> Result<Value, ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, _) = parse(&mut working_set, None, source.as_bytes(), false, &[]);
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let input_as_pipeline_data = match input {
|
||||
Some(input) => PipelineData::Value(input, None),
|
||||
None => PipelineData::new(Span::test_data()),
|
||||
};
|
||||
|
||||
eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
input_as_pipeline_data,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.map(|x| x.into_value(Span::test_data()))
|
||||
}
|
||||
|
||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
let exit_code = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
|
||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
}
|
||||
|
||||
pub fn eval_env_change_hook(
|
||||
env_change_hook: Option<Value>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(hook) = env_change_hook {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols: env_names,
|
||||
vals: hook_values,
|
||||
..
|
||||
} => {
|
||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let after = stack
|
||||
.get_env_var(engine_state, env_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
if before != after {
|
||||
eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||
hook_value,
|
||||
)?;
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_name.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::TypeMismatch(
|
||||
"record for the 'env_change' hook".to_string(),
|
||||
x.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_hook(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<(String, Value)>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_span = value.span()?;
|
||||
|
||||
let condition_path = PathMember::String {
|
||||
val: "condition".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
let code_path = PathMember::String {
|
||||
val: "code".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
eval_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
}
|
||||
Value::Record { .. } => {
|
||||
let do_run_hook =
|
||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
||||
match condition {
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
match run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
arguments.clone(),
|
||||
block_span,
|
||||
) {
|
||||
Ok(value) => match value {
|
||||
Value::Bool { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"boolean output".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always run the hook
|
||||
true
|
||||
};
|
||||
|
||||
if do_run_hook {
|
||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
||||
Value::String {
|
||||
val,
|
||||
span: source_span,
|
||||
} => {
|
||||
let (block, delta, vars) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||
|
||||
for (name, val) in arguments {
|
||||
let var_id = working_set.add_variable(
|
||||
name.as_bytes().to_vec(),
|
||||
val.span()?,
|
||||
Type::Any,
|
||||
);
|
||||
|
||||
vars.push((var_id, val));
|
||||
}
|
||||
|
||||
let (output, err) =
|
||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"valid source code".into(),
|
||||
"source code with syntax errors".into(),
|
||||
source_span,
|
||||
));
|
||||
}
|
||||
|
||||
(output, working_set.render(), vars)
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
let input = PipelineData::new(value_span);
|
||||
|
||||
let var_ids: Vec<VarId> = vars
|
||||
.into_iter()
|
||||
.map(|(var_id, val)| {
|
||||
stack.add_var(var_id, val);
|
||||
var_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
for var_id in var_ids.iter() {
|
||||
stack.vars.remove(var_id);
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block or string".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block, record, or list of records".into(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<(String, Value)>,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
if let Some(arg) = arguments.get(idx) {
|
||||
callee_stack.add_var(*var_id, arg.1.clone())
|
||||
} else {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"This hook block has too many parameters".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
val => {
|
||||
// If all went fine, preserve the environment of the called block
|
||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||
|
||||
// remove env vars that are present in the caller but not in the callee
|
||||
// (the callee hid them)
|
||||
for var in caller_env_vars.iter() {
|
||||
if !callee_stack.has_env_var(engine_state, var) {
|
||||
stack.remove_env_var(engine_state, var);
|
||||
}
|
||||
}
|
||||
|
||||
// add new env vars from callee to caller
|
||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||
stack.add_env_var(var, value);
|
||||
}
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
match io::stdout().write_all(seq.as_bytes()) {
|
||||
Ok(it) => it,
|
||||
@ -456,62 +981,33 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_hook(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<Value>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
run_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span,
|
||||
..
|
||||
} => run_hook_block(engine_state, stack, *block_id, arguments, *span),
|
||||
x => match x.span() {
|
||||
Ok(span) => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
span,
|
||||
)),
|
||||
_ => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
Span { start: 0, end: 0 },
|
||||
)),
|
||||
},
|
||||
}
|
||||
lazy_static! {
|
||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||
static ref DRIVE_PATH_REGEX: Regex =
|
||||
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<Value>,
|
||||
span: Span,
|
||||
) -> Result<(), ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||
fn looks_like_path(orig: &str) -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
callee_stack.add_var(*var_id, arguments[idx].clone())
|
||||
if DRIVE_PATH_REGEX.is_match(orig).unwrap_or(false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
_ => Ok(()),
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\')
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_path_windows_drive_path_works() {
|
||||
let on_windows = cfg!(windows);
|
||||
assert_eq!(looks_like_path("C:"), on_windows);
|
||||
assert_eq!(looks_like_path("D:\\"), on_windows);
|
||||
assert_eq!(looks_like_path("E:/"), on_windows);
|
||||
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
|
||||
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
|
||||
}
|
||||
|
@ -9,18 +9,35 @@ use nu_protocol::{
|
||||
};
|
||||
#[cfg(windows)]
|
||||
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.
|
||||
//
|
||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
||||
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||
pub fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
||||
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) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
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 has_pwd = false;
|
||||
|
||||
// Write all the env vars into a fake file
|
||||
for (name, val) in vars {
|
||||
if name == "PWD" {
|
||||
has_pwd = true;
|
||||
}
|
||||
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
||||
}
|
||||
|
||||
if !has_pwd {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
put_env_to_fake_file("PWD", &cwd.to_string_lossy(), &mut fake_env_file);
|
||||
}
|
||||
Err(e) => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
"Current directory not found".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!("Retrieving current directory failed: {:?}", e)),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
match init_cwd.to_str() {
|
||||
Some(cwd) => {
|
||||
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
|
||||
}
|
||||
None => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
"Current directory is not a valid utf-8 path".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!(
|
||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||
init_cwd
|
||||
)),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,16 +224,11 @@ pub fn eval_source(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let cwd = 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()
|
||||
}
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error_new(engine_state, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
@ -234,7 +242,7 @@ pub fn eval_source(
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false) {
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
@ -284,6 +292,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 {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
@ -297,6 +314,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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -314,6 +342,7 @@ mod test {
|
||||
]
|
||||
.into_iter(),
|
||||
&mut engine_state,
|
||||
Path::new("t"),
|
||||
);
|
||||
|
||||
let env = engine_state.render_env_vars();
|
||||
|
674
crates/nu-cli/tests/completions.rs
Normal file
674
crates/nu-cli/tests/completions.rs
Normal file
@ -0,0 +1,674 @@
|
||||
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-command ", 9);
|
||||
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)
|
||||
}
|
@ -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_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context(&dir);
|
||||
let mut engine_state = create_default_context();
|
||||
|
||||
// New stack
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// New delta state
|
||||
let delta = StateDelta::new(&engine_state);
|
||||
|
||||
// Add pwd as env var
|
||||
stack.add_env_var(
|
||||
"PWD".to_string(),
|
||||
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
},
|
||||
);
|
||||
|
||||
// Merge delta
|
||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
let expected_len = expected.len();
|
||||
let suggestions_len = suggestions.len();
|
||||
if expected_len != suggestions_len {
|
||||
panic!(
|
||||
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||
Suggestions: {suggestions:#?} \n\
|
||||
Expected: {expected:#?}\n"
|
||||
)
|
||||
}
|
||||
expected.iter().zip(suggestions).for_each(|it| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
@ -89,7 +95,7 @@ pub fn merge_input(
|
||||
dir: PathBuf,
|
||||
) -> Result<(), ShellError> {
|
||||
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, &[]);
|
||||
|
||||
@ -97,8 +103,11 @@ pub fn merge_input(
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
assert!(eval_block(
|
||||
&engine_state,
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::Value(
|
||||
@ -112,6 +121,6 @@ pub fn merge_input(
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Merge delta
|
||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
||||
// Merge environment into the permanent state
|
||||
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]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Color configuration code used by Nushell"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.63.1"
|
||||
version = "0.69.1"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
||||
nu-ansi-term = "0.45.1"
|
||||
nu-json = { path = "../nu-json", version = "0.63.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.63.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.69.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.69.1" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug)]
|
||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct NuStyle {
|
||||
pub fg: Option<String>,
|
||||
pub bg: Option<String>,
|
||||
@ -11,12 +11,12 @@ pub struct NuStyle {
|
||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||
// get the nu_ansi_term::Color foreground color
|
||||
let fg_color = match nu_style.fg {
|
||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
||||
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
|
||||
_ => None,
|
||||
};
|
||||
// get the nu_ansi_term::Color background color
|
||||
let bg_color = match nu_style.bg {
|
||||
Some(bg) => color_from_hex(&bg).expect("error with background color"),
|
||||
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
|
||||
_ => None,
|
||||
};
|
||||
// get the attributes
|
||||
|
@ -1,45 +1,49 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Nushell's built-in commands"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.63.1"
|
||||
version = "0.69.1"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.63.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.63.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.63.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.63.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.63.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.63.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.63.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.63.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.63.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.63.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.63.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.63.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.63.1" }
|
||||
nu-ansi-term = "0.45.1"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.69.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.69.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.69.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.69.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.69.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.69.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.69.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.69.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.69.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.69.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.69.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.69.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
num-format = { version = "0.4.0" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
alphanumeric-sort = "1.4.4"
|
||||
base64 = "0.13.0"
|
||||
byteorder = "1.4.3"
|
||||
bytesize = "1.1.0"
|
||||
calamine = "0.18.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
|
||||
chrono-humanize = "0.2.1"
|
||||
chrono-tz = "0.6.1"
|
||||
crossterm = "0.23.0"
|
||||
chrono-tz = "0.6.3"
|
||||
crossterm = "0.24.0"
|
||||
csv = "1.1.6"
|
||||
dialoguer = "0.9.0"
|
||||
digest = "0.10.0"
|
||||
dtparse = "1.2.0"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.30"
|
||||
fancy-regex = "0.10.0"
|
||||
filesize = "0.2.0"
|
||||
filetime = "0.2.15"
|
||||
fs_extra = "1.2.0"
|
||||
@ -47,45 +51,47 @@ htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
indexmap = { version="1.7", features=["serde-1"] }
|
||||
Inflector = "0.11"
|
||||
is-root = "0.1.2"
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
||||
lscolors = { version = "0.12.0", features = ["crossterm"]}
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
meval = "0.2.0"
|
||||
mime = "0.3.16"
|
||||
notify = "4.0.17"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
num-traits = "0.2.14"
|
||||
pathdiff = "0.2.1"
|
||||
powierza-coefficient = "1.0"
|
||||
quick-xml = "0.22"
|
||||
powierza-coefficient = "1.0.1"
|
||||
quick-xml = "0.23.0"
|
||||
rand = "0.8"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "6.3.0"
|
||||
same-file = "1.0.6"
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_ini = "0.2.0"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
serde_yaml = "0.9.4"
|
||||
sha2 = "0.10.0"
|
||||
# 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.23.5"
|
||||
terminal_size = "0.1.17"
|
||||
thiserror = "1.0.29"
|
||||
titlecase = "1.1.0"
|
||||
sysinfo = "0.26.2"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
toml = "0.5.8"
|
||||
unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
which = { version = "4.2.2", optional = true }
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main", features = ["bashisms"]}
|
||||
wax = { version = "0.4.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
which = { version = "4.3.0", optional = true }
|
||||
reedline = { version = "0.12.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "2.0.0"
|
||||
@ -96,15 +102,43 @@ version = "2.1.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.polars]
|
||||
version = "0.21.1"
|
||||
# path = "../../../../polars/polars"
|
||||
version = "0.23.2"
|
||||
optional = true
|
||||
features = [
|
||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
||||
"rolling_window", "strings", "rows", "random",
|
||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
||||
"dynamic_groupby"
|
||||
"arg_where",
|
||||
"checked_arithmetic",
|
||||
"concat_str",
|
||||
"cross_join",
|
||||
"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.37.0"
|
||||
features = [
|
||||
"alloc",
|
||||
"Win32_Foundation",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_SystemServices",
|
||||
]
|
||||
|
||||
[features]
|
||||
@ -115,10 +149,12 @@ dataframe = ["polars", "num"]
|
||||
database = ["sqlparser", "rusqlite"]
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.11.0", default-features = false }
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
proptest = "1.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
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<()> {
|
||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
||||
let hash = get_git_hash().unwrap_or_default();
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
||||
Ok(String::from_utf8(out.stdout)
|
||||
.expect("could not convert stdout to string")
|
||||
.trim()
|
||||
.to_string())
|
||||
fn get_git_hash() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.ok()
|
||||
.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 {})
|
||||
}
|
||||
}
|
167
crates/nu-command/src/bytes/add.rs
Normal file
167
crates/nu-command/src/bytes/add.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
added_data: Vec<u8>,
|
||||
index: Option<usize>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesAdd;
|
||||
|
||||
impl Command for BytesAdd {
|
||||
fn name(&self) -> &str {
|
||||
"bytes add"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes add")
|
||||
.required("data", SyntaxShape::Binary, "the binary to add")
|
||||
.named(
|
||||
"index",
|
||||
SyntaxShape::Int,
|
||||
"index to insert binary data",
|
||||
Some('i'),
|
||||
)
|
||||
.switch("end", "add to the end of binary", Some('e'))
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Add specified bytes to the input"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["append", "truncate", "padding"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
||||
let end = call.has_flag("end");
|
||||
|
||||
let arg = Arguments {
|
||||
added_data,
|
||||
index,
|
||||
end,
|
||||
column_paths,
|
||||
};
|
||||
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
|
||||
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
|
||||
example: "0x[FF AA AA] | bytes add 0x[11] -e",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0xAA, 0x11],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
|
||||
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
|
||||
match args.index {
|
||||
None => {
|
||||
if args.end {
|
||||
let mut added_data = args.added_data.clone();
|
||||
let mut result = input.to_vec();
|
||||
result.append(&mut added_data);
|
||||
Value::Binary { val: result, span }
|
||||
} else {
|
||||
let mut result = args.added_data.clone();
|
||||
let mut input = input.to_vec();
|
||||
result.append(&mut input);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
Some(mut indx) => {
|
||||
let inserted_index = if args.end {
|
||||
input.len().saturating_sub(indx)
|
||||
} else {
|
||||
if indx > input.len() {
|
||||
indx = input.len()
|
||||
}
|
||||
indx
|
||||
};
|
||||
let mut result = vec![];
|
||||
let mut prev_data = input[..inserted_index].to_vec();
|
||||
result.append(&mut prev_data);
|
||||
let mut added_data = args.added_data.clone();
|
||||
result.append(&mut added_data);
|
||||
let mut after_data = input[inserted_index..].to_vec();
|
||||
result.append(&mut after_data);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesAdd {})
|
||||
}
|
||||
}
|
281
crates/nu-command/src/bytes/at.rs
Normal file
281
crates/nu-command/src/bytes/at.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesAt;
|
||||
|
||||
struct Arguments {
|
||||
start: isize,
|
||||
end: isize,
|
||||
arg_span: Span,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
|
||||
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
|
||||
let (start, end, span) = match range {
|
||||
Value::List { mut vals, span } => {
|
||||
if vals.len() != 2 {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"More than two indices given".to_string(),
|
||||
span,
|
||||
));
|
||||
} else {
|
||||
let end = vals.pop().expect("Already check has size 2");
|
||||
let end = match end {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
let start = vals.pop().expect("Already check has size 1");
|
||||
let start = match start {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
(start, end, span)
|
||||
}
|
||||
}
|
||||
Value::String { val, span } => {
|
||||
let splitted_result = val.split_once(',');
|
||||
match splitted_result {
|
||||
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
||||
None => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let start: isize = if start.is_empty() || start == "_" {
|
||||
0
|
||||
} else {
|
||||
match start.trim().parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
let end: isize = if end.is_empty() || end == "_" {
|
||||
isize::max_value()
|
||||
} else {
|
||||
match end.trim().parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok((start, end, span))
|
||||
}
|
||||
|
||||
impl Command for BytesAt {
|
||||
fn name(&self) -> &str {
|
||||
"bytes at"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes at")
|
||||
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally get bytes by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["slice"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let range: Value = call.req(engine_state, stack, 0)?;
|
||||
let (start, end, arg_span) = parse_range(range, call.head)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
start,
|
||||
end,
|
||||
arg_span,
|
||||
column_paths,
|
||||
};
|
||||
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Alternatively, you can use the form",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Drop the last `n` characters from the string",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x33, 0x44, 0x55],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Get the remaining characters from a starting index",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10, 0x01, 0x13],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Get the characters from the beginning until ending index",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x33, 0x44, 0x55, 0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Or the characters from the beginning until ending index inside a table",
|
||||
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x11, 0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let len: isize = input.len() as isize;
|
||||
|
||||
let start: isize = if arg.start < 0 {
|
||||
arg.start + len
|
||||
} else {
|
||||
arg.start
|
||||
};
|
||||
let end: isize = if arg.end < 0 {
|
||||
std::cmp::max(len + arg.end, 0)
|
||||
} else {
|
||||
arg.end
|
||||
};
|
||||
|
||||
if start < len && end >= 0 {
|
||||
match start.cmp(&end) {
|
||||
Ordering::Equal => Value::Binary { val: vec![], span },
|
||||
Ordering::Greater => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"End must be greater than or equal to Start".to_string(),
|
||||
arg.arg_span,
|
||||
),
|
||||
},
|
||||
Ordering::Less => Value::Binary {
|
||||
val: {
|
||||
let input_iter = input.iter().copied().skip(start as usize);
|
||||
if end == isize::max_value() {
|
||||
input_iter.collect()
|
||||
} else {
|
||||
input_iter.take((end - start) as usize).collect()
|
||||
}
|
||||
},
|
||||
span,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Value::Binary { val: vec![], span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesAt {})
|
||||
}
|
||||
}
|
81
crates/nu-command/src/bytes/build_.rs
Normal file
81
crates/nu-command/src/bytes/build_.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesBuild;
|
||||
|
||||
impl Command for BytesBuild {
|
||||
fn name(&self) -> &str {
|
||||
"bytes build"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create bytes from the arguments."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["concatenate", "join"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("bytes build")
|
||||
.rest("rest", SyntaxShape::Any, "list of bytes")
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
|
||||
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x01, 0x02, 0x03, 0x04],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let mut output = vec![];
|
||||
for expr in call.positional_iter() {
|
||||
let val = eval_expression(engine_state, stack, expr)?;
|
||||
match val {
|
||||
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"only support expression which yields to binary data".to_string(),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Binary {
|
||||
val: output,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesBuild {})
|
||||
}
|
||||
}
|
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes;
|
||||
|
||||
impl Command for Bytes {
|
||||
fn name(&self) -> &str {
|
||||
"bytes"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes").category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Various commands for working with byte data"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Bytes;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Bytes {})
|
||||
}
|
||||
}
|
127
crates/nu-command/src/bytes/collect.rs
Normal file
127
crates/nu-command/src/bytes/collect.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BytesCollect;
|
||||
|
||||
impl Command for BytesCollect {
|
||||
fn name(&self) -> &str {
|
||||
"bytes collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes collect")
|
||||
.optional(
|
||||
"separator",
|
||||
SyntaxShape::Binary,
|
||||
"optional separator to use when creating binary",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Concatenate multiple binary into a single binary, with an optional separator between each"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["join", "concatenate"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
|
||||
// input should be a list of binary data.
|
||||
let mut output_binary = vec![];
|
||||
for value in input {
|
||||
match value {
|
||||
Value::Binary { mut val, .. } => {
|
||||
output_binary.append(&mut val);
|
||||
// manually concat
|
||||
// TODO: make use of std::slice::Join when it's available in stable.
|
||||
if let Some(sep) = &separator {
|
||||
let mut work_sep = sep.clone();
|
||||
output_binary.append(&mut work_sep)
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"The element type is {}, this command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match separator {
|
||||
None => Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data()),
|
||||
Some(sep) => {
|
||||
if output_binary.is_empty() {
|
||||
Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
// have push one extra separator in previous step, pop them out.
|
||||
for _ in sep {
|
||||
let _ = output_binary.pop();
|
||||
}
|
||||
Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a byte array from input",
|
||||
example: "[0x[11] 0x[13 15]] | bytes collect",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x11, 0x13, 0x15],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Create a byte array from input with a separator",
|
||||
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesCollect {})
|
||||
}
|
||||
}
|
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesEndsWith;
|
||||
|
||||
impl Command for BytesEndsWith {
|
||||
fn name(&self) -> &str {
|
||||
"bytes ends-with"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes ends-with")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check if bytes ends with a pattern"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_paths,
|
||||
};
|
||||
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[11]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.ends_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesEndsWith {})
|
||||
}
|
||||
}
|
210
crates/nu-command/src/bytes/index_of.rs
Normal file
210
crates/nu-command/src/bytes/index_of.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
all: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesIndexOf;
|
||||
|
||||
impl Command for BytesIndexOf {
|
||||
fn name(&self) -> &str {
|
||||
"bytes index-of"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes index-of")
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::Binary,
|
||||
"the pattern to find index of",
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally returns index of pattern in string by column paths",
|
||||
)
|
||||
.switch("all", "returns all matched index", Some('a'))
|
||||
.switch("end", "search from the end of the binary", Some('e'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
end: call.has_flag("end"),
|
||||
all: call.has_flag("all"),
|
||||
column_paths,
|
||||
};
|
||||
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Returns index of pattern in bytes",
|
||||
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
|
||||
result: Some(Value::test_int(1)),
|
||||
},
|
||||
Example {
|
||||
description: "Returns index of pattern, search from end",
|
||||
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
|
||||
result: Some(Value::test_int(6)),
|
||||
},
|
||||
Example {
|
||||
description: "Returns all matched index",
|
||||
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Returns all matched index, searching from end",
|
||||
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Returns index of pattern for specific column",
|
||||
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::test_int(0),
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::test_int(-1),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
if arg.all {
|
||||
search_all_index(input, &arg.pattern, arg.end, span)
|
||||
} else {
|
||||
let mut iter = input.windows(arg.pattern.len());
|
||||
|
||||
if arg.end {
|
||||
Value::Int {
|
||||
val: iter
|
||||
.rev()
|
||||
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
|
||||
.unwrap_or(-1),
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: iter
|
||||
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||
.map(|x| x as i64)
|
||||
.unwrap_or(-1),
|
||||
span,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
|
||||
let mut result = vec![];
|
||||
if from_end {
|
||||
let (mut left, mut right) = (
|
||||
input.len() as isize - pattern.len() as isize,
|
||||
input.len() as isize,
|
||||
);
|
||||
while left >= 0 {
|
||||
if &input[left as usize..right as usize] == pattern {
|
||||
result.push(Value::Int {
|
||||
val: left as i64,
|
||||
span,
|
||||
});
|
||||
left -= pattern.len() as isize;
|
||||
right -= pattern.len() as isize;
|
||||
} else {
|
||||
left -= 1;
|
||||
right -= 1;
|
||||
}
|
||||
}
|
||||
Value::List { vals: result, span }
|
||||
} else {
|
||||
// doing find stuff.
|
||||
let (mut left, mut right) = (0, pattern.len());
|
||||
let input_len = input.len();
|
||||
let pattern_len = pattern.len();
|
||||
while right <= input_len {
|
||||
if &input[left..right] == pattern {
|
||||
result.push(Value::Int {
|
||||
val: left as i64,
|
||||
span,
|
||||
});
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
} else {
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Value::List { vals: result, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesIndexOf {})
|
||||
}
|
||||
}
|
98
crates/nu-command/src/bytes/length.rs
Normal file
98
crates/nu-command/src/bytes/length.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesLen;
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for BytesLen {
|
||||
fn name(&self) -> &str {
|
||||
"bytes length"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes length")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find length of binary by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output the length of any bytes in the pipeline"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["size", "count"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_paths };
|
||||
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "0x[1F FF AA AB] | bytes length",
|
||||
result: Some(Value::test_int(4)),
|
||||
},
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(4), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
|
||||
Value::Int {
|
||||
val: input.len() as i64,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesLen {})
|
||||
}
|
||||
}
|
98
crates/nu-command/src/bytes/mod.rs
Normal file
98
crates/nu-command/src/bytes/mod.rs
Normal file
@ -0,0 +1,98 @@
|
||||
mod add;
|
||||
mod at;
|
||||
mod build_;
|
||||
mod bytes_;
|
||||
mod collect;
|
||||
mod ends_with;
|
||||
mod index_of;
|
||||
mod length;
|
||||
mod remove;
|
||||
mod replace;
|
||||
mod reverse;
|
||||
mod starts_with;
|
||||
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use add::BytesAdd;
|
||||
pub use at::BytesAt;
|
||||
pub use build_::BytesBuild;
|
||||
pub use bytes_::Bytes;
|
||||
pub use collect::BytesCollect;
|
||||
pub use ends_with::BytesEndsWith;
|
||||
pub use index_of::BytesIndexOf;
|
||||
pub use length::BytesLen;
|
||||
pub use remove::BytesRemove;
|
||||
pub use replace::BytesReplace;
|
||||
pub use reverse::BytesReverse;
|
||||
pub use starts_with::BytesStartsWith;
|
||||
|
||||
trait BytesArgument {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
|
||||
}
|
||||
|
||||
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
|
||||
fn operate<C, A>(
|
||||
cmd: C,
|
||||
mut arg: A,
|
||||
input: PipelineData,
|
||||
span: Span,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
) -> Result<PipelineData, ShellError>
|
||||
where
|
||||
A: BytesArgument + Send + Sync + 'static,
|
||||
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
|
||||
{
|
||||
match arg.take_column_paths() {
|
||||
None => input.map(
|
||||
move |v| match v {
|
||||
Value::Binary {
|
||||
val,
|
||||
span: val_span,
|
||||
} => cmd(&val, &arg, val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
},
|
||||
},
|
||||
ctrlc,
|
||||
),
|
||||
Some(column_paths) => {
|
||||
let arg = Arc::new(arg);
|
||||
input.map(
|
||||
move |mut v| {
|
||||
for path in &column_paths {
|
||||
let opt = arg.clone();
|
||||
let r = v.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| {
|
||||
match old {
|
||||
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
}}}),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
}
|
||||
}
|
||||
v
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
196
crates/nu-command/src/bytes/remove.rs
Normal file
196
crates/nu-command/src/bytes/remove.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesRemove;
|
||||
|
||||
impl Command for BytesRemove {
|
||||
fn name(&self) -> &str {
|
||||
"bytes remove"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes remove")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally remove bytes by column paths",
|
||||
)
|
||||
.switch("end", "remove from end of binary", Some('e'))
|
||||
.switch("all", "remove occurrences of finding binary", Some('a'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove bytes"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["search", "shift", "switch"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if pattern_to_remove.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"the pattern to remove cannot be empty".to_string(),
|
||||
pattern_to_remove.span,
|
||||
));
|
||||
}
|
||||
|
||||
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
|
||||
let arg = Arguments {
|
||||
pattern: pattern_to_remove,
|
||||
end: call.has_flag("end"),
|
||||
column_paths,
|
||||
all: call.has_flag("all"),
|
||||
};
|
||||
|
||||
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove contents",
|
||||
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove all occurrences of find binary",
|
||||
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xBB],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove occurrences of find binary from end",
|
||||
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove all occurrences of find binary in table",
|
||||
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x17, 0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let mut result = vec![];
|
||||
let remove_all = arg.all;
|
||||
let input_len = input.len();
|
||||
let pattern_len = arg.pattern.len();
|
||||
|
||||
// Note:
|
||||
// remove_all from start and end will generate the same result.
|
||||
// so we'll put `remove_all` relative logic into else clouse.
|
||||
if arg.end && !remove_all {
|
||||
let (mut left, mut right) = (
|
||||
input.len() as isize - arg.pattern.len() as isize,
|
||||
input.len() as isize,
|
||||
);
|
||||
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
|
||||
result.push(input[right as usize - 1]);
|
||||
left -= 1;
|
||||
right -= 1;
|
||||
}
|
||||
// append the remaining thing to result, this can be happeneed when
|
||||
// we have something to remove and remove_all is False.
|
||||
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||
result.append(&mut remain);
|
||||
result = result.into_iter().rev().collect();
|
||||
Value::Binary { val: result, span }
|
||||
} else {
|
||||
let (mut left, mut right) = (0, arg.pattern.len());
|
||||
while right <= input_len {
|
||||
if input[left..right] == arg.pattern {
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
if !remove_all {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
result.push(input[left]);
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
// append the remaing thing to result, this can happened when
|
||||
// we have something to remove and remove_all is False.
|
||||
let mut remain = input[left..].to_vec();
|
||||
result.append(&mut remain);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesRemove {})
|
||||
}
|
||||
}
|
171
crates/nu-command/src/bytes/replace.rs
Normal file
171
crates/nu-command/src/bytes/replace.rs
Normal file
@ -0,0 +1,171 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
find: Vec<u8>,
|
||||
replace: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesReplace;
|
||||
|
||||
impl Command for BytesReplace {
|
||||
fn name(&self) -> &str {
|
||||
"bytes replace"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes replace")
|
||||
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find and replace text by column paths",
|
||||
)
|
||||
.switch("all", "replace all occurrences of find binary", Some('a'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find and replace binary"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["search", "shift", "switch"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if find.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"the pattern to find cannot be empty".to_string(),
|
||||
find.span,
|
||||
));
|
||||
}
|
||||
|
||||
let arg = Arguments {
|
||||
find: find.item,
|
||||
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
||||
column_paths,
|
||||
all: call.has_flag("all"),
|
||||
};
|
||||
|
||||
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Find and replace contents",
|
||||
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find binary",
|
||||
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find binary in table",
|
||||
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x13, 0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x17, 0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let mut replaced = vec![];
|
||||
let replace_all = arg.all;
|
||||
|
||||
// doing find-and-replace stuff.
|
||||
let (mut left, mut right) = (0, arg.find.len());
|
||||
let input_len = input.len();
|
||||
let pattern_len = arg.find.len();
|
||||
while right <= input_len {
|
||||
if input[left..right] == arg.find {
|
||||
let mut to_replace = arg.replace.clone();
|
||||
replaced.append(&mut to_replace);
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
if !replace_all {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
replaced.push(input[left]);
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut remain = input[left..].to_vec();
|
||||
replaced.append(&mut remain);
|
||||
Value::Binary {
|
||||
val: replaced,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesReplace {})
|
||||
}
|
||||
}
|
104
crates/nu-command/src/bytes/reverse.rs
Normal file
104
crates/nu-command/src/bytes/reverse.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesReverse;
|
||||
|
||||
impl Command for BytesReverse {
|
||||
fn name(&self) -> &str {
|
||||
"bytes reverse"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes reverse")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Reverse every bytes in the pipeline"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "inverse", "flip"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_paths };
|
||||
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Reverse bytes `0x[1F FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes reverse",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Reverse bytes `0x[FF AA AA]`",
|
||||
example: "0x[FF AA AA] | bytes reverse",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value {
|
||||
let mut reversed_input = input.to_vec();
|
||||
reversed_input.reverse();
|
||||
Value::Binary {
|
||||
val: reversed_input,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesReverse {})
|
||||
}
|
||||
}
|
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesStartsWith;
|
||||
|
||||
impl Command for BytesStartsWith {
|
||||
fn name(&self) -> &str {
|
||||
"bytes starts-with"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes starts-with")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check if bytes starts with a pattern"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_paths,
|
||||
};
|
||||
operate(
|
||||
starts_with,
|
||||
arg,
|
||||
input,
|
||||
call.head,
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F FF AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.starts_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesStartsWith {})
|
||||
}
|
||||
}
|
@ -215,8 +215,8 @@ fn histogram_impl(
|
||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||
for (val, count) in counter.into_iter() {
|
||||
let quantile = match calc_method {
|
||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
||||
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||
};
|
||||
|
||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||
|
@ -29,7 +29,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "binary", "bytes", "bin"]
|
||||
vec!["convert", "bytes"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::{generate_strftime_list, parse_date_from_string};
|
||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
@ -7,9 +8,6 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use crate::generate_strftime_list;
|
||||
use crate::parse_date_from_string;
|
||||
|
||||
struct Arguments {
|
||||
timezone: Option<Spanned<String>>,
|
||||
offset: Option<Spanned<i64>>,
|
||||
@ -105,7 +103,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "date", "time", "timezone", "UTC"]
|
||||
vec!["convert", "timezone", "UTC"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -148,6 +146,15 @@ impl Command for SubCommand {
|
||||
example: "1614434140 | into datetime -o +9",
|
||||
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(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -251,10 +258,19 @@ fn action(
|
||||
|
||||
return match timezone {
|
||||
// default to UTC
|
||||
None => Value::Date {
|
||||
val: Utc.timestamp(ts, 0).into(),
|
||||
span: head,
|
||||
},
|
||||
None => {
|
||||
// be able to convert chrono::Utc::now()
|
||||
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 {
|
||||
Zone::Utc => Value::Date {
|
||||
val: Utc.timestamp(ts, 0).into(),
|
||||
@ -295,7 +311,7 @@ fn action(
|
||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::Date { val: d, span: head },
|
||||
Err(reason) => {
|
||||
return Value::Error {
|
||||
Value::Error {
|
||||
error: ShellError::CantConvert(
|
||||
format!("could not parse as datetime using format '{}'", dt.0),
|
||||
reason.to_string(),
|
||||
|
@ -42,7 +42,7 @@ impl Command for SubCommand {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert string to integer in table",
|
||||
description: "Convert string to decimal in table",
|
||||
example: "[[num]; ['5.01']] | into decimal num",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
@ -54,15 +54,20 @@ impl Command for SubCommand {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to integer",
|
||||
description: "Convert string to decimal",
|
||||
example: "'1.345' | into decimal",
|
||||
result: Some(Value::test_float(1.345)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert decimal to integer",
|
||||
description: "Convert decimal to decimal",
|
||||
example: "'-5.9' | into decimal",
|
||||
result: Some(Value::test_float(-5.9)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert boolean to decimal",
|
||||
example: "true | into decimal",
|
||||
result: Some(Value::test_float(1.0)),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -118,6 +123,13 @@ fn action(input: &Value, head: Span) -> Value {
|
||||
val: *v as f64,
|
||||
span: *span,
|
||||
},
|
||||
Value::Bool { val: b, span } => Value::Float {
|
||||
val: match b {
|
||||
true => 1.0,
|
||||
false => 0.0,
|
||||
},
|
||||
span: *span,
|
||||
},
|
||||
other => {
|
||||
let span = other.span();
|
||||
match span {
|
||||
|
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath, Expr},
|
||||
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)]
|
||||
@ -16,6 +17,12 @@ impl Command for SubCommand {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into duration")
|
||||
.named(
|
||||
"convert",
|
||||
SyntaxShape::String,
|
||||
"convert duration into another duration",
|
||||
Some('c'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -28,6 +35,10 @@ impl Command for SubCommand {
|
||||
"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> {
|
||||
vec!["convert", "time", "period"]
|
||||
}
|
||||
@ -102,6 +113,15 @@ impl Command for SubCommand {
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to a named duration",
|
||||
example: "'7min' | into duration --convert sec",
|
||||
result: Some(Value::String {
|
||||
val: "420 sec".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -113,17 +133,21 @@ fn into_duration(
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
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)?;
|
||||
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, head)
|
||||
action(&v, &convert_to_unit, 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)));
|
||||
let d = convert_to_unit.clone();
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, &d, head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
}
|
||||
@ -136,7 +160,149 @@ 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<i64, ShellError> {
|
||||
match (from_unit, to_unit) {
|
||||
("ns", "ns") => Ok(val),
|
||||
("ns", "us") => Ok(val / 1000),
|
||||
("ns", "ms") => Ok(val / 1000 / 1000),
|
||||
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
|
||||
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
|
||||
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
|
||||
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
|
||||
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
|
||||
("us", "ns") => Ok(val * 1000),
|
||||
("us", "us") => Ok(val),
|
||||
("us", "ms") => Ok(val / 1000),
|
||||
("us", "sec") => Ok(val / 1000 / 1000),
|
||||
("us", "min") => Ok(val / 1000 / 1000 / 60),
|
||||
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
|
||||
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
|
||||
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
|
||||
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
|
||||
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
||||
|
||||
("ms", "ns") => Ok(val * 1000 * 1000),
|
||||
("ms", "us") => Ok(val * 1000),
|
||||
("ms", "ms") => Ok(val),
|
||||
("ms", "sec") => Ok(val / 1000),
|
||||
("ms", "min") => Ok(val / 1000 / 60),
|
||||
("ms", "hr") => Ok(val / 1000 / 60 / 60),
|
||||
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
|
||||
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
|
||||
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
|
||||
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
|
||||
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
|
||||
|
||||
("sec", "ns") => Ok(val * 1000 * 1000 * 1000),
|
||||
("sec", "us") => Ok(val * 1000 * 1000),
|
||||
("sec", "ms") => Ok(val * 1000),
|
||||
("sec", "sec") => Ok(val),
|
||||
("sec", "min") => Ok(val / 60),
|
||||
("sec", "hr") => Ok(val / 60 / 60),
|
||||
("sec", "day") => Ok(val / 60 / 60 / 24),
|
||||
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
|
||||
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
|
||||
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
|
||||
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
|
||||
|
||||
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60),
|
||||
("min", "us") => Ok(val * 1000 * 1000 * 60),
|
||||
("min", "ms") => Ok(val * 1000 * 60),
|
||||
("min", "sec") => Ok(val * 60),
|
||||
("min", "min") => Ok(val),
|
||||
("min", "hr") => Ok(val / 60),
|
||||
("min", "day") => Ok(val / 60 / 24),
|
||||
("min", "wk") => Ok(val / 60 / 24 / 7),
|
||||
("min", "month") => Ok(val / 60 / 24 / 30),
|
||||
("min", "yr") => Ok(val / 60 / 24 / 365),
|
||||
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
|
||||
|
||||
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
|
||||
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
|
||||
("hr", "ms") => Ok(val * 1000 * 60 * 60),
|
||||
("hr", "sec") => Ok(val * 60 * 60),
|
||||
("hr", "min") => Ok(val * 60),
|
||||
("hr", "hr") => Ok(val),
|
||||
("hr", "day") => Ok(val / 24),
|
||||
("hr", "wk") => Ok(val / 24 / 7),
|
||||
("hr", "month") => Ok(val / 24 / 30),
|
||||
("hr", "yr") => Ok(val / 24 / 365),
|
||||
("hr", "dec") => Ok(val / 10 / 24 / 365),
|
||||
|
||||
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
|
||||
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
|
||||
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
|
||||
("day", "sec") => Ok(val * 60 * 60 * 24),
|
||||
("day", "min") => Ok(val * 60 * 24),
|
||||
("day", "hr") => Ok(val * 24),
|
||||
("day", "day") => Ok(val),
|
||||
("day", "wk") => Ok(val / 7),
|
||||
("day", "month") => Ok(val / 30),
|
||||
("day", "yr") => Ok(val / 365),
|
||||
("day", "dec") => Ok(val / 10 / 365),
|
||||
|
||||
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
|
||||
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
|
||||
("wk", "min") => Ok(val * 60 * 24 * 7),
|
||||
("wk", "hr") => Ok(val * 24 * 7),
|
||||
("wk", "day") => Ok(val * 7),
|
||||
("wk", "wk") => Ok(val),
|
||||
("wk", "month") => Ok(val / 4),
|
||||
("wk", "yr") => Ok(val / 52),
|
||||
("wk", "dec") => Ok(val / 10 / 52),
|
||||
|
||||
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
|
||||
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
|
||||
("month", "min") => Ok(val * 60 * 24 * 30),
|
||||
("month", "hr") => Ok(val * 24 * 30),
|
||||
("month", "day") => Ok(val * 30),
|
||||
("month", "wk") => Ok(val * 4),
|
||||
("month", "month") => Ok(val),
|
||||
("month", "yr") => Ok(val / 12),
|
||||
("month", "dec") => Ok(val / 10 / 12),
|
||||
|
||||
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
|
||||
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
|
||||
("yr", "min") => Ok(val * 60 * 24 * 365),
|
||||
("yr", "hr") => Ok(val * 24 * 365),
|
||||
("yr", "day") => Ok(val * 365),
|
||||
("yr", "wk") => Ok(val * 52),
|
||||
("yr", "month") => Ok(val * 12),
|
||||
("yr", "yr") => Ok(val),
|
||||
("yr", "dec") => Ok(val / 10),
|
||||
|
||||
_ => 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 Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||
if let Expr::Int(x) = value.expr {
|
||||
@ -155,21 +321,108 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShellError::CantConvert(
|
||||
Err(ShellError::CantConvertWithValue(
|
||||
"duration".to_string(),
|
||||
"string".to_string(),
|
||||
s.to_string(),
|
||||
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>>, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Duration { .. } => input.clone(),
|
||||
Value::String { val, .. } => match string_to_duration(val, span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
},
|
||||
Value::Duration {
|
||||
val: _val_num,
|
||||
span: _value_span,
|
||||
} => {
|
||||
if let Some(_to_unit) = convert_to_unit {
|
||||
Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"Cannot convert from a Value::Duration right now. Try making it a string."
|
||||
.into(),
|
||||
span,
|
||||
),
|
||||
}
|
||||
} 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) => Value::String {
|
||||
val: format!("{} {}", 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 {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"'into duration' does not support this input".into(),
|
||||
@ -195,8 +448,9 @@ mod test {
|
||||
let span = Span::test_data();
|
||||
let word = Value::test_string("3ns");
|
||||
let expected = Value::Duration { val: 3, span };
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -208,8 +462,9 @@ mod test {
|
||||
val: 4 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -221,8 +476,9 @@ mod test {
|
||||
val: 5 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -234,8 +490,9 @@ mod test {
|
||||
val: 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -247,8 +504,9 @@ mod test {
|
||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -260,8 +518,9 @@ mod test {
|
||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -273,8 +532,9 @@ mod test {
|
||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -286,8 +546,9 @@ mod test {
|
||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "number", "size", "bytes"]
|
||||
vec!["convert", "number", "bytes"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -8,6 +8,7 @@ use nu_protocol::{
|
||||
struct Arguments {
|
||||
radix: Option<Value>,
|
||||
column_paths: Vec<CellPath>,
|
||||
little_endian: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -21,6 +22,7 @@ impl Command for SubCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into int")
|
||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||
.switch("little-endian", "use little-endian byte decoding", None)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -100,6 +102,21 @@ impl Command for SubCommand {
|
||||
example: "'FF' | into int -r 16",
|
||||
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)),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -114,6 +131,7 @@ fn into_int(
|
||||
|
||||
let options = Arguments {
|
||||
radix: call.get_flag(engine_state, stack, "radix")?,
|
||||
little_endian: call.has_flag("little-endian"),
|
||||
column_paths: call.rest(engine_state, stack, 0)?,
|
||||
};
|
||||
|
||||
@ -135,13 +153,13 @@ fn into_int(
|
||||
input.map(
|
||||
move |v| {
|
||||
if options.column_paths.is_empty() {
|
||||
action(&v, head, radix)
|
||||
action(&v, head, radix, options.little_endian)
|
||||
} 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)),
|
||||
Box::new(move |old| action(old, head, radix, options.little_endian)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
@ -155,7 +173,7 @@ fn into_int(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
|
||||
match input {
|
||||
Value::Int { val: _, .. } => {
|
||||
if radix == 10 {
|
||||
@ -166,7 +184,32 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
}
|
||||
Value::Filesize { val, .. } => Value::Int { val: *val, span },
|
||||
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,
|
||||
},
|
||||
Value::String { val, .. } => {
|
||||
@ -190,8 +233,38 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
val: val.timestamp(),
|
||||
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 {
|
||||
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 +273,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
let i = match input {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
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) {
|
||||
Ok(x) => return Value::Int { val: x, span: head },
|
||||
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()
|
||||
}
|
||||
@ -217,10 +310,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 },
|
||||
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 +351,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
};
|
||||
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),
|
||||
Err(_) => match a_string.parse::<f64>() {
|
||||
Ok(f) => Ok(f as i64),
|
||||
@ -266,7 +373,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
"int".to_string(),
|
||||
"string".to_string(),
|
||||
span,
|
||||
None,
|
||||
Some(format!(
|
||||
r#"string "{}" does not represent a valid integer"#,
|
||||
trimmed
|
||||
)),
|
||||
)),
|
||||
},
|
||||
},
|
||||
@ -291,21 +401,21 @@ mod test {
|
||||
let word = Value::test_string("10");
|
||||
let expected = Value::test_int(10);
|
||||
|
||||
let actual = action(&word, Span::test_data(), 10);
|
||||
let actual = action(&word, Span::test_data(), 10, false);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_binary_to_integer() {
|
||||
let s = Value::test_string("0b101");
|
||||
let actual = action(&s, Span::test_data(), 10);
|
||||
let actual = action(&s, Span::test_data(), 10, false);
|
||||
assert_eq!(actual, Value::test_int(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_hex_to_integer() {
|
||||
let s = Value::test_string("0xFF");
|
||||
let actual = action(&s, Span::test_data(), 16);
|
||||
let actual = action(&s, Span::test_data(), 16, false);
|
||||
assert_eq!(actual, Value::test_int(255));
|
||||
}
|
||||
|
||||
@ -313,7 +423,7 @@ mod test {
|
||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||
let integer_str = Value::test_string("36anra");
|
||||
|
||||
let actual = action(&integer_str, Span::test_data(), 10);
|
||||
let actual = action(&integer_str, Span::test_data(), 10, false);
|
||||
|
||||
assert_eq!(actual.get_type(), Error)
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Value,
|
||||
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
||||
use nu_utils::get_system_locale;
|
||||
use num_format::ToFormattedString;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -38,7 +38,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "str", "text"]
|
||||
vec!["convert", "text"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -53,6 +53,14 @@ impl Command for SubCommand {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
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 {
|
||||
description: "convert decimal to string and round to nearest integer",
|
||||
example: "1.7 | into string -d 0",
|
||||
@ -208,12 +216,8 @@ pub fn action(
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
let res = if group_digits {
|
||||
format_int(*val) // int.to_formatted_string(*locale)
|
||||
} else {
|
||||
val.to_string()
|
||||
};
|
||||
|
||||
let decimal_value = digits.unwrap_or(0) as usize;
|
||||
let res = format_int(*val, group_digits, decimal_value);
|
||||
Value::String { val: res, span }
|
||||
}
|
||||
Value::Float { val, .. } => {
|
||||
@ -247,6 +251,15 @@ pub fn action(
|
||||
val: input.into_string(", ", config),
|
||||
span,
|
||||
},
|
||||
Value::Error { error } => Value::String {
|
||||
val: {
|
||||
match into_code(error) {
|
||||
Some(code) => code,
|
||||
None => "".to_string(),
|
||||
}
|
||||
},
|
||||
span,
|
||||
},
|
||||
Value::Nothing { .. } => Value::String {
|
||||
val: "".to_string(),
|
||||
span,
|
||||
@ -279,21 +292,29 @@ pub fn action(
|
||||
},
|
||||
}
|
||||
}
|
||||
fn format_int(int: i64) -> String {
|
||||
int.to_string()
|
||||
|
||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
||||
// #[cfg(windows)]
|
||||
// {
|
||||
// int.to_formatted_string(&Locale::en)
|
||||
// }
|
||||
// #[cfg(not(windows))]
|
||||
// {
|
||||
// match SystemLocale::default() {
|
||||
// Ok(locale) => int.to_formatted_string(&locale),
|
||||
// Err(_) => int.to_formatted_string(&Locale::en),
|
||||
// }
|
||||
// }
|
||||
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
|
||||
let locale = get_system_locale();
|
||||
|
||||
let str = if group_digits {
|
||||
int.to_formatted_string(&locale)
|
||||
} else {
|
||||
int.to_string()
|
||||
};
|
||||
|
||||
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)]
|
||||
|
@ -27,13 +27,17 @@ impl Command for Alias {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["abbr", "aka", "fn", "func", "function"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
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 {
|
||||
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 {
|
||||
|
@ -28,7 +28,33 @@ impl Command for DefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_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 {
|
||||
|
@ -1,7 +1,9 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Do;
|
||||
@ -23,6 +25,11 @@ impl Command for Do {
|
||||
"ignore errors as the block runs",
|
||||
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")
|
||||
.category(Category::Core)
|
||||
}
|
||||
@ -37,6 +44,7 @@ impl Command for Do {
|
||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
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 block = engine_state.get_block(block.block_id);
|
||||
@ -85,7 +93,7 @@ impl Command for Do {
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
ignore_errors,
|
||||
ignore_errors || capture_errors,
|
||||
);
|
||||
|
||||
if ignore_errors {
|
||||
@ -93,6 +101,11 @@ impl Command for Do {
|
||||
Ok(x) => Ok(x),
|
||||
Err(_) => Ok(PipelineData::new(call.head)),
|
||||
}
|
||||
} else if capture_errors {
|
||||
match result {
|
||||
Ok(x) => Ok(x),
|
||||
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
||||
}
|
||||
} else {
|
||||
result
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ impl Command for Echo {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -2,8 +2,7 @@ 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,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -16,7 +15,12 @@ impl Command for ErrorMake {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -25,7 +29,7 @@ impl Command for ErrorMake {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["err", "panic", "crash", "throw"]
|
||||
vec!["panic", "crash", "throw"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -33,42 +37,32 @@ impl Command for ErrorMake {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
let unspanned = call.has_flag("unspanned");
|
||||
|
||||
if let Some(arg) = arg {
|
||||
Ok(make_error(&arg, span)
|
||||
.map(|err| Value::Error { error: err })
|
||||
.unwrap_or_else(|| Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
})
|
||||
.into_pipeline_data())
|
||||
if unspanned {
|
||||
Err(make_error(&arg, None).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
input.map(
|
||||
move |value| {
|
||||
make_error(&value, span)
|
||||
.map(|err| Value::Error { error: err })
|
||||
.unwrap_or_else(|| Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
let msg = value.get_data_by_key("msg");
|
||||
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(
|
||||
message,
|
||||
label_text,
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
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(
|
||||
message,
|
||||
"originates from here".to_string(),
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
|
@ -18,12 +18,12 @@ impl Command for ExportCommand {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
@ -27,7 +27,7 @@ impl Command for ExportAlias {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -28,7 +28,7 @@ impl Command for ExportDef {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -28,7 +28,33 @@ impl Command for ExportDefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -23,7 +23,7 @@ impl Command for ExportExtern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -3,35 +3,26 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportEnv;
|
||||
pub struct ExportUse;
|
||||
|
||||
impl Command for ExportEnv {
|
||||
impl Command for ExportUse {
|
||||
fn name(&self) -> &str {
|
||||
"export env"
|
||||
"export use"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export a block from a module that will be evaluated as an environment variable when imported."
|
||||
"Use definitions from a module and export them from this module"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export env")
|
||||
.required(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"name of the environment variable",
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the environment variable definition",
|
||||
)
|
||||
Signature::build("export use")
|
||||
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -45,16 +36,19 @@ impl Command for ExportEnv {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
//TODO: Add the env to stack
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Import and evaluate environment variable from a module",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
description: "Re-export a command from another module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
module eggs { export use spam foo }
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
val: "foo".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
@ -23,7 +23,7 @@ impl Command for Extern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -46,7 +46,7 @@ impl Command for For {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -1,14 +1,17 @@
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::{
|
||||
Color::{Default, Red, White},
|
||||
Style,
|
||||
};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Help;
|
||||
|
||||
@ -81,23 +84,29 @@ fn help(
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
let commands = engine_state.get_decl_ids_sorted(false);
|
||||
let config = engine_state.get_config();
|
||||
let color_hm = get_color_config(config);
|
||||
let default_style = Style::new().fg(Default).on(Default);
|
||||
let string_style = match color_hm.get("string") {
|
||||
Some(style) => style,
|
||||
None => &default_style,
|
||||
};
|
||||
|
||||
if let Some(f) = find {
|
||||
let org_search_string = f.item.clone();
|
||||
let search_string = f.item.to_lowercase();
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
for decl_id in commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
let sig = decl.signature().update_from_command(decl.borrow());
|
||||
|
||||
let key = sig.name;
|
||||
let usage = sig.usage;
|
||||
let search_terms = sig.search_terms;
|
||||
|
||||
let matches_term = if !search_terms.is_empty() {
|
||||
search_terms
|
||||
.iter()
|
||||
@ -106,13 +115,16 @@ fn help(
|
||||
false
|
||||
};
|
||||
|
||||
if key.to_lowercase().contains(&search_string)
|
||||
|| usage.to_lowercase().contains(&search_string)
|
||||
|| matches_term
|
||||
{
|
||||
let key_match = key.to_lowercase().contains(&search_string);
|
||||
let usage_match = usage.to_lowercase().contains(&search_string);
|
||||
if key_match || usage_match || matches_term {
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: key,
|
||||
val: if key_match {
|
||||
highlight_search_string(&key, &org_search_string, string_style)?
|
||||
} else {
|
||||
key
|
||||
},
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -130,7 +142,7 @@ fn help(
|
||||
|
||||
cols.push("is_custom".into());
|
||||
vals.push(Value::Bool {
|
||||
val: decl.get_block_id().is_some(),
|
||||
val: decl.is_custom_command(),
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -142,7 +154,11 @@ fn help(
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String {
|
||||
val: usage,
|
||||
val: if usage_match {
|
||||
highlight_search_string(&usage, &org_search_string, string_style)?
|
||||
} else {
|
||||
usage
|
||||
},
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -151,7 +167,30 @@ fn help(
|
||||
Value::nothing(head)
|
||||
} else {
|
||||
Value::String {
|
||||
val: search_terms.join(", "),
|
||||
val: if matches_term {
|
||||
search_terms
|
||||
.iter()
|
||||
.map(|term| {
|
||||
if term.to_lowercase().contains(&search_string) {
|
||||
match highlight_search_string(
|
||||
term,
|
||||
&org_search_string,
|
||||
string_style,
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
string_style.paint(term.to_string()).to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
string_style.paint(term.to_string()).to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
} else {
|
||||
search_terms.join(", ")
|
||||
},
|
||||
span: head,
|
||||
}
|
||||
});
|
||||
@ -204,7 +243,7 @@ fn help(
|
||||
|
||||
cols.push("is_custom".into());
|
||||
vals.push(Value::Bool {
|
||||
val: decl.get_block_id().is_some(),
|
||||
val: decl.is_custom_command(),
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -303,3 +342,70 @@ You can also learn more at https://www.nushell.sh/book/"#;
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||
pub fn highlight_search_string(
|
||||
haystack: &str,
|
||||
needle: &str,
|
||||
string_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){}", needle);
|
||||
let regex = match Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Could not compile regex".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
// strip haystack to remove existing ansi style
|
||||
let stripped_haystack: String = match strip_ansi_escapes::strip(haystack) {
|
||||
Ok(i) => String::from_utf8(i).unwrap_or_else(|_| String::from(haystack)),
|
||||
Err(_) => String::from(haystack),
|
||||
};
|
||||
let mut last_match_end = 0;
|
||||
let style = Style::new().fg(White).on(Red);
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(stripped_haystack.as_str()) {
|
||||
match cap {
|
||||
Ok(capture) => {
|
||||
let start = match capture.get(0) {
|
||||
Some(acap) => acap.start(),
|
||||
None => 0,
|
||||
};
|
||||
let end = match capture.get(0) {
|
||||
Some(acap) => acap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||
last_match_end = end;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error with regular expression capture".into(),
|
||||
e.to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -19,14 +19,14 @@ impl Command for Hide {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide symbols in the current scope"
|
||||
"Hide definitions in the current scope"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Symbols are hidden by priority: First aliases, then custom commands, then environment variables.
|
||||
r#"Definitions are hidden by priority: First aliases, then custom commands.
|
||||
|
||||
This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let import_pattern = if let Some(Expression {
|
||||
let env_var_name = if let Some(Expression {
|
||||
expr: Expr::ImportPattern(pat),
|
||||
..
|
||||
}) = call.positional_nth(0)
|
||||
{
|
||||
pat
|
||||
Spanned {
|
||||
item: String::from_utf8_lossy(&pat.head.name).to_string(),
|
||||
span: pat.head.span,
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::GenericError(
|
||||
"Unexpected import".into(),
|
||||
@ -56,78 +59,7 @@ This command is a parser keyword. For details, check:
|
||||
));
|
||||
};
|
||||
|
||||
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
||||
};
|
||||
|
||||
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
|
||||
// The first word is a module
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let env_vars_to_hide = if import_pattern.members.is_empty() {
|
||||
module.env_vars_with_head(&import_pattern.head.name)
|
||||
} else {
|
||||
match &import_pattern.members[0] {
|
||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
||||
ImportPatternMember::Name { name, span } => {
|
||||
let mut output = vec![];
|
||||
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
ImportPatternMember::List { names } => {
|
||||
let mut output = vec![];
|
||||
|
||||
for (name, span) in names {
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (name, _) in env_vars_to_hide {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.span()));
|
||||
};
|
||||
|
||||
if stack.remove_env_var(engine_state, &name).is_none() {
|
||||
return Err(ShellError::NotFound(
|
||||
call.positional_nth(0)
|
||||
.expect("already checked for present positional")
|
||||
.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
|
||||
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
|
||||
{
|
||||
// TODO: we may want to error in the future
|
||||
}
|
||||
stack.remove_env_var(engine_state, &env_var_name.item);
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user