forked from extern/nushell
Compare commits
419 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
||||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: context
|
id: context
|
||||||
attributes:
|
attributes:
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: "When you want a new feature for something that doesn't already exist"
|
description: "When you want a new feature for something that doesn't already exist"
|
||||||
|
labels: "enhancement"
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
|
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Question
|
||||||
|
description: "When you have a question to ask"
|
||||||
|
labels: "question"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Question
|
||||||
|
description: Leave your question here
|
||||||
|
placeholder: |
|
||||||
|
A clear and concise question
|
||||||
|
Example: Is there any equivalent of bash's $CDPATH in Nu?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context and details
|
||||||
|
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
|
||||||
|
validations:
|
||||||
|
required: false
|
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
# Tests
|
# 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:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
|
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@ -23,12 +23,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
# makes ci use rust-toolchain.toml
|
||||||
profile: minimal
|
# with:
|
||||||
toolchain: ${{ matrix.rust }}
|
# profile: minimal
|
||||||
override: true
|
# toolchain: ${{ matrix.rust }}
|
||||||
components: rustfmt, clippy
|
# override: true
|
||||||
|
# components: rustfmt, clippy
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
with:
|
with:
|
||||||
@ -74,17 +75,16 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
# makes ci use rust-toolchain.toml
|
||||||
profile: minimal
|
# with:
|
||||||
toolchain: ${{ matrix.rust }}
|
# profile: minimal
|
||||||
override: true
|
# toolchain: ${{ matrix.rust }}
|
||||||
|
# override: true
|
||||||
|
|
||||||
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
|
- uses: Swatinem/rust-cache@v1
|
||||||
# TODO: investigate why the cache was so big
|
with:
|
||||||
# - uses: Swatinem/rust-cache@v1
|
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
||||||
# with:
|
|
||||||
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@ -111,11 +111,12 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
# makes ci use rust-toolchain.toml
|
||||||
profile: minimal
|
# with:
|
||||||
toolchain: ${{ matrix.rust }}
|
# profile: minimal
|
||||||
override: true
|
# toolchain: ${{ matrix.rust }}
|
||||||
|
# override: true
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
with:
|
with:
|
||||||
@ -135,10 +136,7 @@ jobs:
|
|||||||
- run: python -m pip install tox
|
- run: python -m pip install tox
|
||||||
|
|
||||||
- name: Install virtualenv
|
- name: Install virtualenv
|
||||||
run: |
|
run: git clone https://github.com/pypa/virtualenv.git
|
||||||
git clone https://github.com/kubouch/virtualenv.git && \
|
|
||||||
cd virtualenv && \
|
|
||||||
git checkout engine-q-update
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
@ -164,11 +162,12 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
# makes ci use rust-toolchain.toml
|
||||||
profile: minimal
|
# with:
|
||||||
toolchain: ${{ matrix.rust }}
|
# profile: minimal
|
||||||
override: true
|
# toolchain: ${{ matrix.rust }}
|
||||||
|
# override: true
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
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 {
|
} else {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
sudo apt install musl-tools -y
|
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
# Build for Windows without static-link-openssl feature
|
# Build for Windows without static-link-openssl feature
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in ['windows-latest'] {
|
if $os in ['windows-latest'] {
|
||||||
if ($flags | str trim | empty?) {
|
if ($flags | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra
|
cargo build --release --all --target $target --features=extra
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra $flags
|
cargo build --release --all --target $target --features=extra $flags
|
||||||
@ -80,7 +80,7 @@ let ver = if $os == 'windows-latest' {
|
|||||||
} else {
|
} else {
|
||||||
(do -i { ./output/nu -c 'version' }) | str collect
|
(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)'
|
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||||
} else { $ver }
|
} else { $ver }
|
||||||
|
|
||||||
@ -102,8 +102,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
|
||||||
$'(char nl)Download less related stuffs...'; hr-line
|
$'(char nl)Download less related stuffs...'; hr-line
|
||||||
curl https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o $'($dist)\less.exe'
|
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
||||||
curl https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o $'($dist)\LICENSE-for-less.txt'
|
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||||
|
|
||||||
# Create Windows msi release package
|
# Create Windows msi release package
|
||||||
if (get-env _EXTRA_) == 'msi' {
|
if (get-env _EXTRA_) == 'msi' {
|
||||||
@ -113,7 +113,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
cd $src; hr-line
|
cd $src; hr-line
|
||||||
# Wix need the binaries be stored in target/release/
|
# Wix need the binaries be stored in target/release/
|
||||||
cp -r $'($dist)/*' target/release/
|
cp -r $'($dist)/*' target/release/
|
||||||
cargo install cargo-wix --version 0.3.2
|
cargo install cargo-wix --version 0.3.3
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
echo $'::set-output name=archive::($wixRelease)'
|
echo $'::set-output name=archive::($wixRelease)'
|
||||||
|
|
||||||
@ -124,14 +124,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
7z a $archive *
|
7z a $archive *
|
||||||
print $'archive: ---> ($archive)';
|
print $'archive: ---> ($archive)';
|
||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | empty?) {
|
if not ($pkg | is-empty) {
|
||||||
echo $'::set-output name=archive::($pkg | get 0)'
|
echo $'::set-output name=archive::($pkg | get 0)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def 'cargo-build-nu' [ options: string ] {
|
def 'cargo-build-nu' [ options: string ] {
|
||||||
if ($options | str trim | empty?) {
|
if ($options | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||||
@ -143,7 +143,7 @@ def 'hr-line' [
|
|||||||
--blank-line(-b): bool
|
--blank-line(-b): bool
|
||||||
] {
|
] {
|
||||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||||
if $blank-line { char nl }
|
if $blank_line { char nl }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the specified env key's value or ''
|
# Get the specified env key's value or ''
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -70,9 +70,9 @@ jobs:
|
|||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v1
|
uses: hustcer/setup-nu@v2.1
|
||||||
with:
|
with:
|
||||||
version: 0.63.0
|
version: 0.68.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
6
.github/workflows/winget-submission.yml
vendored
6
.github/workflows/winget-submission.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
run: |
|
run: |
|
||||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
|
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -29,3 +29,9 @@ debian/nu/
|
|||||||
# Coverage tools
|
# Coverage tools
|
||||||
lcov.info
|
lcov.info
|
||||||
tarpaulin-report.html
|
tarpaulin-report.html
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/*
|
||||||
|
*.rsproj
|
||||||
|
*.rsproj.user
|
||||||
|
*.sln
|
@ -66,3 +66,9 @@ cargo build
|
|||||||
```shell
|
```shell
|
||||||
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"
|
||||||
|
```
|
||||||
|
1670
Cargo.lock
generated
1670
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"
|
readme = "README.md"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.64.0"
|
version = "0.69.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -28,34 +28,37 @@ members = [
|
|||||||
"crates/nu_plugin_gstat",
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu_plugin_query",
|
||||||
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.21", features = ["serde"] }
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
ctrlc = "3.2.1"
|
ctrlc = "3.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "4.5.0"
|
miette = "5.1.0"
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.64.0" }
|
nu-cli = { path="./crates/nu-cli", version = "0.69.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.64.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.69.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.64.0" }
|
nu-command = { path="./crates/nu-command", version = "0.69.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.64.0" }
|
nu-engine = { path="./crates/nu-engine", version = "0.69.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.64.0" }
|
nu-json = { path="./crates/nu-json", version = "0.69.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.64.0" }
|
nu-parser = { path="./crates/nu-parser", version = "0.69.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.64.0" }
|
nu-path = { path="./crates/nu-path", version = "0.69.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.64.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.69.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.64.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.69.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.64.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.69.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.64.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.69.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.64.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.69.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.64.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.69.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.64.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.69.0" }
|
||||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||||
pretty_env_logger = "0.4.0"
|
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
|
simplelog = "0.12.0"
|
||||||
|
time = "0.3.12"
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||||
# Our dependencies don't use OpenSSL on Windows
|
# Our dependencies don't use OpenSSL on Windows
|
||||||
@ -63,13 +66,13 @@ openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
|||||||
signal-hook = { version = "0.3.14", default-features = false }
|
signal-hook = { version = "0.3.14", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="./crates/nu-test-support", version = "0.64.0" }
|
nu-test-support = { path="./crates/nu-test-support", version = "0.69.0" }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
assert_cmd = "2.0.2"
|
assert_cmd = "2.0.2"
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
serial_test = "0.5.1"
|
serial_test = "0.8.0"
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
rstest = "0.12.0"
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
@ -77,9 +80,9 @@ winres = "0.1"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||||
|
extra = ["default", "dataframe", "database"]
|
||||||
default = ["plugin", "which-support", "trash-support"]
|
default = ["plugin", "which-support", "trash-support"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
extra = ["default", "dataframe", "database"]
|
|
||||||
wasi = []
|
wasi = []
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
@ -119,3 +122,6 @@ debug = false
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||||
|
|
||||||
> register -e json ./nu_plugin_query
|
> register ./nu_plugin_query
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 7.3 KiB |
@ -10,6 +10,7 @@ NU_PLUGINS=(
|
|||||||
'nu_plugin_gstat'
|
'nu_plugin_gstat'
|
||||||
'nu_plugin_inc'
|
'nu_plugin_inc'
|
||||||
'nu_plugin_query'
|
'nu_plugin_query'
|
||||||
|
'nu_plugin_custom_values'
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Building nushell"
|
echo "Building nushell"
|
||||||
|
@ -24,9 +24,13 @@ cargo build
|
|||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd ..\..\crates\nu_plugin_query
|
@cd ..\..\crates\nu_plugin_query
|
||||||
|
|
||||||
echo Building nu_plugin_query.exe
|
echo Building nu_plugin_query.exe
|
||||||
cargo build
|
cargo build
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
|
@cd ..\..\crates\nu_plugin_custom_values
|
||||||
|
echo Building nu_plugin_custom_values.exe
|
||||||
|
cargo build
|
||||||
|
@echo.
|
||||||
|
|
||||||
@cd ..\..
|
@cd ..\..
|
@ -11,6 +11,7 @@ let plugins = [
|
|||||||
nu_plugin_gstat,
|
nu_plugin_gstat,
|
||||||
nu_plugin_query,
|
nu_plugin_query,
|
||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
|
nu_plugin_custom_values,
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
@ -1,33 +1,40 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "CLI-related functionality for Nushell"
|
description = "CLI-related functionality for Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.64.0"
|
version = "0.69.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.64.0" }
|
nu-test-support = { path="../nu-test-support", version = "0.69.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.64.0" }
|
nu-command = { path = "../nu-command", version = "0.69.0" }
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.64.0" }
|
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.64.0" }
|
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.64.0" }
|
nu-parser = { path = "../nu-parser", version = "0.69.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.64.0" }
|
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.64.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
|
||||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||||
crossterm = "0.23.0"
|
|
||||||
miette = { version = "4.5.0", features = ["fancy"] }
|
|
||||||
thiserror = "1.0.29"
|
|
||||||
fuzzy-matcher = "0.3.7"
|
|
||||||
|
|
||||||
log = "0.4"
|
atty = "0.2.14"
|
||||||
|
chrono = "0.4.21"
|
||||||
|
crossterm = "0.24.0"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
|
fuzzy-matcher = "0.3.7"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
chrono = "0.4.19"
|
lazy_static = "1.4.0"
|
||||||
sysinfo = "0.24.1"
|
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]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -5,21 +5,27 @@ use nu_engine::{convert_env_values, eval_block};
|
|||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::Stack;
|
use nu_protocol::engine::Stack;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
PipelineData, Spanned, Value,
|
PipelineData, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
init_cwd: &Path,
|
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
is_perf_true: bool,
|
||||||
table_mode: Option<Value>,
|
table_mode: Option<Value>,
|
||||||
) -> Result<()> {
|
) -> Result<Option<i64>> {
|
||||||
// Run a command (or commands) given to us by the user
|
// Translate environment variables from Strings to Values
|
||||||
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source code
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
@ -39,43 +45,19 @@ pub fn evaluate_commands(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
// Update permanent state
|
||||||
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = engine_state.get_config().clone();
|
// Run the block
|
||||||
if let Some(t_mode) = table_mode {
|
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
config.table_mode = t_mode.as_string()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
|
||||||
Ok(cwd) => {
|
|
||||||
if let Err(e) =
|
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
|
let mut config = engine_state.get_config().clone();
|
||||||
|
if let Some(t_mode) = table_mode {
|
||||||
|
config.table_mode = t_mode.as_string()?;
|
||||||
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -84,11 +66,11 @@ pub fn evaluate_commands(
|
|||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
if is_perf_true {
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(exit_code)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
|||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandCompletion {
|
impl CommandCompletion {
|
||||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
|||||||
_: &StateWorkingSet,
|
_: &StateWorkingSet,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
flat_shape: FlatShape,
|
flat_shape: FlatShape,
|
||||||
|
force_completion_after_space: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
flattened,
|
flattened,
|
||||||
flat_shape,
|
flat_shape,
|
||||||
|
force_completion_after_space,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,19 +46,21 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if !executables.contains(
|
if self.engine_state.config.max_external_completion_results
|
||||||
&item
|
> executables.len() as i64
|
||||||
.path()
|
&& !executables.contains(
|
||||||
.file_name()
|
&item
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
.path()
|
||||||
.unwrap_or_default(),
|
.file_name()
|
||||||
) && matches!(
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
item.path()
|
.unwrap_or_default(),
|
||||||
.file_name()
|
)
|
||||||
.map(|x| match_algorithm
|
&& matches!(
|
||||||
|
item.path().file_name().map(|x| match_algorithm
|
||||||
.matches_str(&x.to_string_lossy(), prefix)),
|
.matches_str(&x.to_string_lossy(), prefix)),
|
||||||
Some(true)
|
Some(true)
|
||||||
) && is_executable::is_executable(&item.path())
|
)
|
||||||
|
&& is_executable::is_executable(&item.path())
|
||||||
{
|
{
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
executables.push(name);
|
executables.push(name);
|
||||||
@ -114,7 +119,8 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
let partial = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
let partial = String::from_utf8_lossy(partial).to_string();
|
let partial = String::from_utf8_lossy(partial).to_string();
|
||||||
let results = if find_externals {
|
|
||||||
|
if find_externals {
|
||||||
let results_external = self
|
let results_external = self
|
||||||
.external_command_completion(&partial, match_algorithm)
|
.external_command_completion(&partial, match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -146,9 +152,7 @@ impl CommandCompletion {
|
|||||||
results
|
results
|
||||||
} else {
|
} else {
|
||||||
results
|
results
|
||||||
};
|
}
|
||||||
|
|
||||||
results
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +209,10 @@ impl Completer for CommandCompletion {
|
|||||||
|| ((span.end - span.start) == 0)
|
|| ((span.end - span.start) == 0)
|
||||||
{
|
{
|
||||||
// we're in a gap or at a command
|
// we're in a gap or at a command
|
||||||
|
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||||
|
{
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
self.complete_commands(
|
self.complete_commands(
|
||||||
working_set,
|
working_set,
|
||||||
span,
|
span,
|
||||||
|
@ -2,10 +2,11 @@ use crate::completions::{
|
|||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||||
};
|
};
|
||||||
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
BlockId, PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||||
use std::str;
|
use std::str;
|
||||||
@ -56,6 +57,67 @@ impl NuCompleter {
|
|||||||
suggestions
|
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> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
@ -63,19 +125,33 @@ impl NuCompleter {
|
|||||||
let initial_line = line.to_string();
|
let initial_line = line.to_string();
|
||||||
new_line.push(b'a');
|
new_line.push(b'a');
|
||||||
let pos = offset + pos;
|
let pos = offset + pos;
|
||||||
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||||
|
|
||||||
for pipeline in output.pipelines.into_iter() {
|
for pipeline in output.pipelines.into_iter() {
|
||||||
for expr in pipeline.expressions {
|
for expr in pipeline.expressions {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
|
let span_offset: usize = alias_offset.iter().sum();
|
||||||
|
let mut spans: Vec<String> = vec![];
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
let alias = if alias_offset.is_empty() {
|
// Read the current spam to string
|
||||||
0
|
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 {
|
} else {
|
||||||
alias_offset[flat_idx]
|
spans.push(current_span_str.to_string());
|
||||||
};
|
}
|
||||||
if pos >= flat.0.start - alias && pos < flat.0.end - alias {
|
|
||||||
|
// Complete based on the last span
|
||||||
|
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||||
// Context variables
|
// Context variables
|
||||||
let most_left_var =
|
let most_left_var =
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
@ -84,42 +160,18 @@ impl NuCompleter {
|
|||||||
let new_span = if flat_idx == 0 {
|
let new_span = if flat_idx == 0 {
|
||||||
Span {
|
Span {
|
||||||
start: flat.0.start,
|
start: flat.0.start,
|
||||||
end: flat.0.end - 1 - alias,
|
end: flat.0.end - 1 - span_offset,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Span {
|
Span {
|
||||||
start: flat.0.start - alias,
|
start: flat.0.start - span_offset,
|
||||||
end: flat.0.end - 1 - alias,
|
end: flat.0.end - 1 - span_offset,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses the prefix
|
// Parses the prefix
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
prefix.remove(pos - (flat.0.start - alias));
|
prefix.remove(pos - (flat.0.start - span_offset));
|
||||||
|
|
||||||
// Completions that depends on the previous expression (e.g: use, source)
|
|
||||||
if flat_idx > 0 {
|
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
|
||||||
// Read the content for the previous expression
|
|
||||||
let prev_expr_str =
|
|
||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
|
||||||
|
|
||||||
// Completion for .nu files
|
|
||||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
|
||||||
let mut completer =
|
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables completion
|
// Variables completion
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
@ -141,8 +193,38 @@ impl NuCompleter {
|
|||||||
|
|
||||||
// Flags completion
|
// Flags completion
|
||||||
if prefix.starts_with(b"-") {
|
if prefix.starts_with(b"-") {
|
||||||
let mut completer = FlagCompletion::new(expr);
|
// Try to complete flag internally
|
||||||
|
let mut completer = FlagCompletion::new(expr.clone());
|
||||||
|
let result = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got no results for internal completion
|
||||||
|
// now we can check if external completer is set and use it
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
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(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -153,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 other types
|
||||||
match &flat.1 {
|
match &flat.1 {
|
||||||
FlatShape::Custom(decl_id) => {
|
FlatShape::Custom(decl_id) => {
|
||||||
@ -185,6 +303,18 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
|
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
flat_shape => {
|
flat_shape => {
|
||||||
let mut completer = CommandCompletion::new(
|
let mut completer = CommandCompletion::new(
|
||||||
self.engine_state.clone(),
|
self.engine_state.clone(),
|
||||||
@ -192,9 +322,10 @@ impl NuCompleter {
|
|||||||
flattened.clone(),
|
flattened.clone(),
|
||||||
// flat_idx,
|
// flat_idx,
|
||||||
flat_shape.clone(),
|
flat_shape.clone(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let out: Vec<_> = self.process_completion(
|
let mut out: Vec<_> = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
prefix.clone(),
|
prefix.clone(),
|
||||||
@ -203,21 +334,30 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
|
||||||
if out.is_empty() {
|
if !out.is_empty() {
|
||||||
let mut completer =
|
return out;
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -225,7 +365,7 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return vec![];
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +375,7 @@ impl ReedlineCompleter for NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MatchedAlias<'a> = Vec<(&'a [u8], &'a [u8])>;
|
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||||
|
|
||||||
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||||
// that `g` is an alias of `git`
|
// that `g` is an alias of `git`
|
||||||
@ -254,6 +394,13 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
|
|||||||
lens -= 1;
|
lens -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !line.is_empty() {
|
||||||
|
let last = line.last().expect("input is empty");
|
||||||
|
if last == &b' ' {
|
||||||
|
output.push(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
output = line.to_vec();
|
output = line.to_vec();
|
||||||
}
|
}
|
||||||
@ -261,7 +408,7 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
|
|||||||
(output, alias_offset)
|
(output, alias_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option<MatchedAlias<'a>> {
|
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||||
let mut vec_names = vec![];
|
let mut vec_names = vec![];
|
||||||
let mut vec_alias = vec![];
|
let mut vec_alias = vec![];
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
@ -269,27 +416,31 @@ fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option
|
|||||||
for (index, character) in input.iter().enumerate() {
|
for (index, character) in input.iter().enumerate() {
|
||||||
if *character == b' ' {
|
if *character == b' ' {
|
||||||
let range = &input[pos..index];
|
let range = &input[pos..index];
|
||||||
vec_names.push(range);
|
vec_names.push(range.to_owned());
|
||||||
pos = index + 1;
|
pos = index + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Push the rest to names vector.
|
// Push the rest to names vector.
|
||||||
if pos < input.len() {
|
if pos < input.len() {
|
||||||
vec_names.push(&input[pos..]);
|
vec_names.push(input[pos..].to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
for name in &vec_names {
|
for name in &vec_names {
|
||||||
if let Some(alias_id) = working_set.find_alias(name) {
|
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||||
let alias_span = working_set.get_alias(alias_id);
|
let alias_span = working_set.get_alias(alias_id);
|
||||||
|
let mut span_vec = vec![];
|
||||||
is_alias = true;
|
is_alias = true;
|
||||||
for alias in alias_span {
|
for alias in alias_span {
|
||||||
let name = working_set.get_span_contents(*alias);
|
let name = working_set.get_span_contents(*alias);
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
vec_alias.push(name);
|
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 {
|
} else {
|
||||||
vec_alias.push(name);
|
vec_alias.push(name.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,3 +503,65 @@ fn most_left_variable(
|
|||||||
|
|
||||||
Some((var, sublevels))
|
Some((var, sublevels))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_value_completions<'a>(
|
||||||
|
list: impl Iterator<Item = &'a Value>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
|
list.filter_map(move |x| {
|
||||||
|
// Match for string values
|
||||||
|
if let Ok(s) = x.as_string() {
|
||||||
|
return Some(Suggestion {
|
||||||
|
value: s,
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match for record values
|
||||||
|
if let Ok((cols, vals)) = x.as_record() {
|
||||||
|
let mut suggestion = Suggestion {
|
||||||
|
value: String::from(""), // Initialize with empty string
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate the cols looking for `value` and `description`
|
||||||
|
cols.iter().zip(vals).for_each(|it| {
|
||||||
|
// Match `value` column
|
||||||
|
if it.0 == "value" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(val_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.value = val_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match `description` column
|
||||||
|
if it.0 == "description" {
|
||||||
|
// Convert the value to string
|
||||||
|
if let Ok(desc_str) = it.1.as_string() {
|
||||||
|
// Update the suggestion value
|
||||||
|
suggestion.description = Some(desc_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Some(suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::completer::map_value_completions;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
|||||||
sort_by: SortBy::None,
|
sort_by: SortBy::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_completions<'a>(
|
|
||||||
&self,
|
|
||||||
list: impl Iterator<Item = &'a Value>,
|
|
||||||
span: Span,
|
|
||||||
offset: usize,
|
|
||||||
) -> Vec<Suggestion> {
|
|
||||||
list.filter_map(move |x| {
|
|
||||||
// Match for string values
|
|
||||||
if let Ok(s) = x.as_string() {
|
|
||||||
return Some(Suggestion {
|
|
||||||
value: s,
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match for record values
|
|
||||||
if let Ok((cols, vals)) = x.as_record() {
|
|
||||||
let mut suggestion = Suggestion {
|
|
||||||
value: String::from(""), // Initialize with empty string
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Iterate the cols looking for `value` and `description`
|
|
||||||
cols.iter().zip(vals).for_each(|it| {
|
|
||||||
// Match `value` column
|
|
||||||
if it.0 == "value" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(val_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.value = val_str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match `description` column
|
|
||||||
if it.0 == "description" {
|
|
||||||
// Convert the value to string
|
|
||||||
if let Ok(desc_str) = it.1.as_string() {
|
|
||||||
// Update the suggestion value
|
|
||||||
suggestion.description = Some(desc_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Some(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CustomCompletion {
|
impl Completer for CustomCompletion {
|
||||||
@ -144,7 +83,7 @@ impl Completer for CustomCompletion {
|
|||||||
.and_then(|val| {
|
.and_then(|val| {
|
||||||
val.as_list()
|
val.as_list()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let options = value.get_data_by_key("options");
|
let options = value.get_data_by_key("options");
|
||||||
@ -189,7 +128,7 @@ impl Completer for CustomCompletion {
|
|||||||
|
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
|
|||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
use crate::util::{eval_source, report_error};
|
use crate::util::{eval_source, report_error};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use log::info;
|
use log::info;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_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 nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@ -15,12 +21,13 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
|||||||
pub fn read_plugin_file(
|
pub fn read_plugin_file(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
is_perf_true: bool,
|
is_perf_true: bool,
|
||||||
) {
|
) {
|
||||||
// Reading signatures from signature file
|
// Reading signatures from signature file
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, storage_path);
|
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
if let Some(plugin_path) = plugin_path {
|
if let Some(plugin_path) = plugin_path {
|
||||||
@ -43,8 +50,23 @@ pub fn read_plugin_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
pub fn add_plugin_file(
|
||||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
engine_state: &mut EngineState,
|
||||||
|
plugin_file: Option<Spanned<String>>,
|
||||||
|
storage_path: &str,
|
||||||
|
) {
|
||||||
|
if let Some(plugin_file) = plugin_file {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
match canonicalize_with(&plugin_file.item, cwd) {
|
||||||
|
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||||
|
Err(_) => {
|
||||||
|
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
@ -69,12 +91,10 @@ pub fn eval_config_contents(
|
|||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::new(Span::new(0, 0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge the delta in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
if let Err(e) =
|
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
|
||||||
{
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ pub fn print_table_or_error(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
mut pipeline_data: PipelineData,
|
mut pipeline_data: PipelineData,
|
||||||
config: &mut Config,
|
config: &mut Config,
|
||||||
) {
|
) -> Option<i64> {
|
||||||
let exit_code = match &mut pipeline_data {
|
let exit_code = match &mut pipeline_data {
|
||||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -77,59 +77,63 @@ pub fn print_table_or_error(
|
|||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Some(decl_id) => {
|
Some(decl_id) => {
|
||||||
let table = engine_state.get_decl(decl_id).run(
|
let command = engine_state.get_decl(decl_id);
|
||||||
engine_state,
|
if command.get_block_id().is_some() {
|
||||||
stack,
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
&Call::new(Span::new(0, 0)),
|
} else {
|
||||||
pipeline_data,
|
let table = command.run(
|
||||||
);
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&Call::new(Span::new(0, 0)),
|
||||||
|
pipeline_data,
|
||||||
|
);
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
for item in table {
|
print_or_exit(table, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
}
|
||||||
}
|
Err(error) => {
|
||||||
Err(error) => {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
for item in pipeline_data {
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
if let Value::Error { error } = item {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
|
||||||
out.push('\n');
|
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure everything has finished
|
// Make sure everything has finished
|
||||||
if let Some(exit_code) = exit_code {
|
if let Some(exit_code) = exit_code {
|
||||||
let _: Vec<_> = exit_code.into_iter().collect();
|
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
|
||||||
|
exit_code
|
||||||
|
.pop()
|
||||||
|
.and_then(|last_exit_code| match last_exit_code {
|
||||||
|
Value::Int { val: code, .. } => Some(code),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||||
|
for item in pipeline_data {
|
||||||
|
if let Value::Error { error } = item {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = item.into_string("\n", config);
|
||||||
|
out.push('\n');
|
||||||
|
|
||||||
|
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
|
|||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
pub use repl::evaluate_repl;
|
pub use repl::evaluate_repl;
|
||||||
|
pub use repl::{eval_env_change_hook, eval_hook};
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
nu_ansi_term::{ansi::RESET, Style},
|
nu_ansi_term::{ansi::RESET, Style},
|
||||||
reedline::{
|
reedline::{
|
||||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||||
Painter, Suggestion,
|
Painter, Suggestion, UndoBehavior,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
|||||||
fn can_partially_complete(
|
fn can_partially_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
_values_updated: bool,
|
_values_updated: bool,
|
||||||
_line_buffer: &mut LineBuffer,
|
_editor: &mut Editor,
|
||||||
_completer: &mut dyn Completer,
|
_completer: &mut dyn Completer,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates menu values
|
/// Updates menu values
|
||||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
||||||
if self.only_buffer_difference {
|
if self.only_buffer_difference {
|
||||||
if let Some(old_string) = &self.input {
|
if let Some(old_string) = &self.input {
|
||||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.values = completer.complete(input, start);
|
self.values = completer.complete(input, start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||||
self.values =
|
self.values = completer.complete(
|
||||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
trimmed_buffer.as_str(),
|
||||||
|
editor.line_buffer().insertion_point(),
|
||||||
|
);
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
|||||||
/// collected from the completer
|
/// collected from the completer
|
||||||
fn update_working_details(
|
fn update_working_details(
|
||||||
&mut self,
|
&mut self,
|
||||||
line_buffer: &mut LineBuffer,
|
editor: &mut Editor,
|
||||||
completer: &mut dyn Completer,
|
completer: &mut dyn Completer,
|
||||||
painter: &Painter,
|
painter: &Painter,
|
||||||
) {
|
) {
|
||||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
|||||||
match event {
|
match event {
|
||||||
MenuEvent::Activate(_) => {
|
MenuEvent::Activate(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.input = Some(line_buffer.get_buffer().to_string());
|
self.input = Some(editor.get_buffer().to_string());
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
}
|
}
|
||||||
MenuEvent::Deactivate => self.active = false,
|
MenuEvent::Deactivate => self.active = false,
|
||||||
MenuEvent::Edit(_) => {
|
MenuEvent::Edit(_) => {
|
||||||
self.reset_position();
|
self.reset_position();
|
||||||
self.update_values(line_buffer, completer);
|
self.update_values(editor, completer);
|
||||||
self.update_examples()
|
self.update_examples()
|
||||||
}
|
}
|
||||||
MenuEvent::NextElement => {
|
MenuEvent::NextElement => {
|
||||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The buffer gets replaced in the Span location
|
/// The buffer gets replaced in the Span location
|
||||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
fn replace_in_buffer(&self, editor: &mut Editor) {
|
||||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||||
let start = span.start.min(line_buffer.len());
|
let start = span.start.min(editor.line_buffer().len());
|
||||||
let end = span.end.min(line_buffer.len());
|
let end = span.end.min(editor.line_buffer().len());
|
||||||
|
|
||||||
let string_len = if let Some(example_index) = self.example_index {
|
let replacement = if let Some(example_index) = self.example_index {
|
||||||
let example = self
|
self.examples
|
||||||
.examples
|
|
||||||
.get(example_index)
|
.get(example_index)
|
||||||
.expect("the example index is always checked");
|
.expect("the example index is always checked")
|
||||||
|
|
||||||
line_buffer.replace(start..end, example);
|
|
||||||
example.len()
|
|
||||||
} else {
|
} else {
|
||||||
line_buffer.replace(start..end, &value);
|
&value
|
||||||
value.len()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = line_buffer.insertion_point();
|
editor.edit_buffer(
|
||||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
|lb| {
|
||||||
line_buffer.set_insertion_point(offset);
|
lb.replace_range(start..end, replacement);
|
||||||
|
let mut offset = lb.insertion_point();
|
||||||
|
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||||
|
lb.set_insertion_point(offset);
|
||||||
|
},
|
||||||
|
UndoBehavior::CreateUndoPoint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,11 +21,19 @@ impl Command for Print {
|
|||||||
"print without inserting a newline for the line ending",
|
"print without inserting a newline for the line ending",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
|
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Prints the values given"
|
"Print the given values to stdout"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||||
|
Since this command has no output, there is no point in piping it with other commands.
|
||||||
|
|
||||||
|
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -41,11 +49,12 @@ impl Command for Print {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag("no-newline");
|
let no_newline = call.has_flag("no-newline");
|
||||||
|
let to_stderr = call.has_flag("stderr");
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg.into_pipeline_data()
|
arg.into_pipeline_data()
|
||||||
.print(engine_state, stack, no_newline)?;
|
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::new(head))
|
Ok(PipelineData::new(head))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::DefaultPrompt;
|
use reedline::DefaultPrompt;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
reedline::{
|
reedline::{
|
||||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||||
@ -86,6 +87,11 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
impl Prompt for NushellPrompt {
|
impl Prompt for NushellPrompt {
|
||||||
fn render_prompt_left(&self) -> Cow<str> {
|
fn render_prompt_left(&self) -> Cow<str> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = enable_vt_processing();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(prompt_string) = &self.left_prompt_string {
|
if let Some(prompt_string) = &self.left_prompt_string {
|
||||||
prompt_string.replace('\n', "\r\n").into()
|
prompt_string.replace('\n', "\r\n").into()
|
||||||
} else {
|
} else {
|
||||||
|
@ -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_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||||
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
|
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||||
|
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||||
|
|
||||||
fn get_prompt_string(
|
fn get_prompt_string(
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
@ -98,6 +102,20 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
is_perf_true,
|
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(
|
let right_prompt_string = get_prompt_string(
|
||||||
PROMPT_COMMAND_RIGHT,
|
PROMPT_COMMAND_RIGHT,
|
||||||
config,
|
config,
|
||||||
|
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
}
|
}
|
||||||
|
"space" => KeyCode::Char(' '),
|
||||||
"down" => KeyCode::Down,
|
"down" => KeyCode::Down,
|
||||||
"up" => KeyCode::Up,
|
"up" => KeyCode::Up,
|
||||||
"left" => KeyCode::Left,
|
"left" => KeyCode::Left,
|
||||||
@ -814,7 +815,6 @@ fn event_from_record(
|
|||||||
) -> Result<ReedlineEvent, ShellError> {
|
) -> Result<ReedlineEvent, ShellError> {
|
||||||
let event = match name {
|
let event = match name {
|
||||||
"none" => ReedlineEvent::None,
|
"none" => ReedlineEvent::None,
|
||||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
|
||||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||||
@ -875,7 +875,16 @@ fn edit_from_record(
|
|||||||
"moveleft" => EditCommand::MoveLeft,
|
"moveleft" => EditCommand::MoveLeft,
|
||||||
"moveright" => EditCommand::MoveRight,
|
"moveright" => EditCommand::MoveRight,
|
||||||
"movewordleft" => EditCommand::MoveWordLeft,
|
"movewordleft" => EditCommand::MoveWordLeft,
|
||||||
|
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
||||||
"movewordright" => EditCommand::MoveWordRight,
|
"movewordright" => EditCommand::MoveWordRight,
|
||||||
|
"movewordrightend" => EditCommand::MoveWordRightEnd,
|
||||||
|
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
|
||||||
|
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
||||||
|
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
||||||
|
"movetoposition" => {
|
||||||
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
|
EditCommand::MoveToPosition(value.as_integer()? as usize)
|
||||||
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", cols, vals, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
@ -888,6 +897,7 @@ fn edit_from_record(
|
|||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
"delete" => EditCommand::Delete,
|
"delete" => EditCommand::Delete,
|
||||||
|
"cutchar" => EditCommand::CutChar,
|
||||||
"backspaceword" => EditCommand::BackspaceWord,
|
"backspaceword" => EditCommand::BackspaceWord,
|
||||||
"deleteword" => EditCommand::DeleteWord,
|
"deleteword" => EditCommand::DeleteWord,
|
||||||
"clear" => EditCommand::Clear,
|
"clear" => EditCommand::Clear,
|
||||||
@ -898,7 +908,11 @@ fn edit_from_record(
|
|||||||
"cuttoend" => EditCommand::CutToEnd,
|
"cuttoend" => EditCommand::CutToEnd,
|
||||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||||
"cutwordleft" => EditCommand::CutWordLeft,
|
"cutwordleft" => EditCommand::CutWordLeft,
|
||||||
|
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||||
"cutwordright" => EditCommand::CutWordRight,
|
"cutwordright" => EditCommand::CutWordRight,
|
||||||
|
"cutbigwordright" => EditCommand::CutBigWordRight,
|
||||||
|
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
|
||||||
|
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
|
||||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||||
"uppercaseword" => EditCommand::UppercaseWord,
|
"uppercaseword" => EditCommand::UppercaseWord,
|
||||||
|
@ -2,26 +2,39 @@ use crate::{
|
|||||||
completions::NuCompleter,
|
completions::NuCompleter,
|
||||||
prompt_update,
|
prompt_update,
|
||||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
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,
|
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 miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::get_color_config;
|
use nu_color_config::get_color_config;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::lex;
|
use nu_parser::{lex, parse};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
ast::PathMember,
|
||||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Value,
|
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||||
|
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||||
|
Spanned, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||||
use std::io::{self, Write};
|
use std::{
|
||||||
use std::{sync::atomic::Ordering, time::Instant};
|
io::{self, Write},
|
||||||
|
sync::atomic::Ordering,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
use strip_ansi_escapes::strip;
|
||||||
use sysinfo::SystemExt;
|
use sysinfo::SystemExt;
|
||||||
|
|
||||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\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\\";
|
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||||
const CMD_FINISHED_MARKER: &str = "\x1b]133;D\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";
|
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||||
|
|
||||||
pub fn evaluate_repl(
|
pub fn evaluate_repl(
|
||||||
@ -29,9 +42,20 @@ pub fn evaluate_repl(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
nushell_path: &str,
|
nushell_path: &str,
|
||||||
is_perf_true: bool,
|
is_perf_true: bool,
|
||||||
|
prerun_command: Option<Spanned<String>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
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 entry_num = 0;
|
||||||
|
|
||||||
let mut nu_prompt = NushellPrompt::new();
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
@ -77,14 +101,21 @@ 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 {
|
if is_perf_true {
|
||||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut line_editor = Reedline::create();
|
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(
|
let history_path = crate::config_files::get_history_path(
|
||||||
nushell_path,
|
nushell_path,
|
||||||
engine_state.config.history_file_format,
|
engine_state.config.history_file_format,
|
||||||
@ -111,6 +142,36 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let sys = sysinfo::System::new();
|
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 {
|
loop {
|
||||||
if is_perf_true {
|
if is_perf_true {
|
||||||
info!(
|
info!(
|
||||||
@ -121,6 +182,14 @@ 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
|
//Reset the ctrl-c handler
|
||||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||||
ctrlc.store(false, Ordering::SeqCst);
|
ctrlc.store(false, Ordering::SeqCst);
|
||||||
@ -130,7 +199,7 @@ pub fn evaluate_repl(
|
|||||||
sig_quit.store(false, Ordering::SeqCst);
|
sig_quit.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
if is_perf_true {
|
if is_perf_true {
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||||
@ -147,7 +216,6 @@ pub fn evaluate_repl(
|
|||||||
engine_state: engine_state.clone(),
|
engine_state: engine_state.clone(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
}))
|
}))
|
||||||
.with_animation(config.animate_prompt)
|
|
||||||
.with_validator(Box::new(NuValidator {
|
.with_validator(Box::new(NuValidator {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_state.clone(),
|
||||||
}))
|
}))
|
||||||
@ -201,7 +269,10 @@ pub fn evaluate_repl(
|
|||||||
if is_perf_true {
|
if is_perf_true {
|
||||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
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 {
|
if is_perf_true {
|
||||||
@ -236,64 +307,22 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
// Right before we start our prompt and take input from the user,
|
// Right before we start our prompt and take input from the user,
|
||||||
// fire the "pre_prompt" hook
|
// fire the "pre_prompt" hook
|
||||||
if let Some(hook) = &config.hooks.pre_prompt {
|
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
report_error_new(engine_state, &err);
|
||||||
report_error(&working_set, &err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, check all the environment variables they ask for
|
// Next, check all the environment variables they ask for
|
||||||
// fire the "env_change" hook
|
// fire the "env_change" hook
|
||||||
if let Some(hook) = config.hooks.env_change.clone() {
|
let config = engine_state.get_config();
|
||||||
match hook {
|
if let Err(error) =
|
||||||
Value::Record {
|
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||||
cols, vals: blocks, ..
|
{
|
||||||
} => {
|
report_error_new(engine_state, &error)
|
||||||
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();
|
|
||||||
|
|
||||||
let shell_integration = config.shell_integration;
|
|
||||||
if shell_integration {
|
|
||||||
run_ansi_sequence(PRE_PROMPT_MARKER)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let config = engine_state.get_config();
|
||||||
let prompt =
|
let prompt =
|
||||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
||||||
|
|
||||||
@ -309,16 +338,19 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let input = line_editor.read_line(prompt);
|
let input = line_editor.read_line(prompt);
|
||||||
|
let shell_integration = config.shell_integration;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
Ok(Signal::Success(s)) => {
|
||||||
|
let hostname = sys.host_name();
|
||||||
let history_supports_meta =
|
let history_supports_meta =
|
||||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||||
if history_supports_meta && !s.is_empty() {
|
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||||
|
{
|
||||||
line_editor
|
line_editor
|
||||||
.update_last_command_context(&|mut c| {
|
.update_last_command_context(&|mut c| {
|
||||||
c.start_timestamp = Some(chrono::Utc::now());
|
c.start_timestamp = Some(chrono::Utc::now());
|
||||||
c.hostname = sys.host_name();
|
c.hostname = hostname.clone();
|
||||||
|
|
||||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||||
c
|
c
|
||||||
@ -326,34 +358,22 @@ pub fn evaluate_repl(
|
|||||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
.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,
|
// Right before we start running the code the user gave us,
|
||||||
// fire the "pre_execution" hook
|
// fire the "pre_execution" hook
|
||||||
if let Some(hook) = &config.hooks.pre_execution {
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
report_error_new(engine_state, &err);
|
||||||
report_error(&working_set, &err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
|
||||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
|
||||||
let path = cwd.as_string()?;
|
|
||||||
// Try to abbreviate string for windows title
|
|
||||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
|
||||||
path.replace(&p.as_path().display().to_string(), "~")
|
|
||||||
} else {
|
|
||||||
path
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set window title too
|
|
||||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
|
||||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
|
||||||
// ESC]1;stringBEL -- Set icon name to string
|
|
||||||
// ESC]2;stringBEL -- Set window title to string
|
|
||||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
@ -364,13 +384,7 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let orig = s.clone();
|
let orig = s.clone();
|
||||||
|
|
||||||
if (orig.starts_with('.')
|
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||||
|| orig.starts_with('~')
|
|
||||||
|| orig.starts_with('/')
|
|
||||||
|| orig.starts_with('\\'))
|
|
||||||
&& path.is_dir()
|
|
||||||
&& tokens.0.len() == 1
|
|
||||||
{
|
|
||||||
// We have an auto-cd
|
// We have an auto-cd
|
||||||
let (path, span) = {
|
let (path, span) = {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
@ -386,6 +400,14 @@ pub fn evaluate_repl(
|
|||||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
(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
|
//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
|
//should probably be a block that loads the information from the state in the overlay
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
@ -413,9 +435,23 @@ pub fn evaluate_repl(
|
|||||||
0
|
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 };
|
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_SHELLS".into(), Value::List { vals: shells, span });
|
||||||
|
stack.add_env_var(
|
||||||
|
"NUSHELL_LAST_SHELL".into(),
|
||||||
|
Value::Int {
|
||||||
|
val: last_shell as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
trace!("eval source: {}", s);
|
trace!("eval source: {}", s);
|
||||||
|
|
||||||
@ -437,15 +473,8 @@ pub fn evaluate_repl(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME: permanent state changes like this hopefully in time can be removed
|
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||||
// 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
|
line_editor
|
||||||
.update_last_command_context(&|mut c| {
|
.update_last_command_context(&|mut c| {
|
||||||
c.duration = Some(cmd_duration);
|
c.duration = Some(cmd_duration);
|
||||||
@ -458,20 +487,69 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
// FIXME: use variant with exit code, if apropriate
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
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) => {
|
Ok(Signal::CtrlC) => {
|
||||||
// `Reedline` clears the line content. New prompt is shown
|
// `Reedline` clears the line content. New prompt is shown
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlD) => {
|
Ok(Signal::CtrlD) => {
|
||||||
// When exiting clear to a new line
|
// When exiting clear to a new line
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
break;
|
break;
|
||||||
@ -480,9 +558,13 @@ pub fn evaluate_repl(
|
|||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
if !message.contains("duration") {
|
if !message.contains("duration") {
|
||||||
println!("Error: {:?}", err);
|
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 {
|
if shell_integration {
|
||||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -491,6 +573,390 @@ pub fn evaluate_repl(
|
|||||||
Ok(())
|
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> {
|
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||||
match io::stdout().write_all(seq.as_bytes()) {
|
match io::stdout().write_all(seq.as_bytes()) {
|
||||||
Ok(it) => it,
|
Ok(it) => it,
|
||||||
@ -515,62 +981,33 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_hook(
|
lazy_static! {
|
||||||
engine_state: &EngineState,
|
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||||
stack: &mut Stack,
|
static ref DRIVE_PATH_REGEX: Regex =
|
||||||
arguments: Vec<Value>,
|
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
|
||||||
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 },
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_hook_block(
|
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||||
engine_state: &EngineState,
|
fn looks_like_path(orig: &str) -> bool {
|
||||||
stack: &mut Stack,
|
#[cfg(windows)]
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
if let Some(var_id) = var_id {
|
if DRIVE_PATH_REGEX.is_match(orig).unwrap_or(false) {
|
||||||
callee_stack.add_var(*var_id, arguments[idx].clone())
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
orig.starts_with('.')
|
||||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
|| orig.starts_with('~')
|
||||||
Value::Error { error } => Err(error),
|
|| orig.starts_with('/')
|
||||||
_ => Ok(()),
|
|| orig.starts_with('\\')
|
||||||
},
|
}
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
#[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);
|
||||||
}
|
}
|
||||||
|
@ -224,16 +224,11 @@ pub fn eval_source(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
Ok(p) => p,
|
set_last_exit_code(stack, 1);
|
||||||
Err(e) => {
|
report_error_new(engine_state, &err);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
return false;
|
||||||
report_error(&working_set, &e);
|
}
|
||||||
get_init_cwd()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(mut pipeline_data) => {
|
Ok(mut pipeline_data) => {
|
||||||
@ -247,7 +242,7 @@ pub fn eval_source(
|
|||||||
set_last_exit_code(stack, 0);
|
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);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
@ -297,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 {
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
match std::env::current_dir() {
|
match std::env::current_dir() {
|
||||||
Ok(cwd) => cwd,
|
Ok(cwd) => cwd,
|
||||||
@ -310,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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
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 ", 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 -", 4);
|
|
||||||
|
|
||||||
assert_eq!(12, suggestions.len());
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"--all".into(),
|
|
||||||
"--du".into(),
|
|
||||||
"--full-paths".into(),
|
|
||||||
"--help".into(),
|
|
||||||
"--long".into(),
|
|
||||||
"--short-names".into(),
|
|
||||||
"-a".into(),
|
|
||||||
"-d".into(),
|
|
||||||
"-f".into(),
|
|
||||||
"-h".into(),
|
|
||||||
"-l".into(),
|
|
||||||
"-s".into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match results
|
|
||||||
match_suggestions(expected, suggestions);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
pub mod support;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
|
||||||
use reedline::Completer;
|
|
||||||
use support::{folder, match_suggestions, new_engine};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn folder_completions() {
|
|
||||||
// Create a new engine
|
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
|
||||||
|
|
||||||
// Instatiate a new completer
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
|
||||||
|
|
||||||
// Test completions for the current folder
|
|
||||||
let target_dir = format!("cd {}", dir_str);
|
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
|
||||||
|
|
||||||
// Create the expected values
|
|
||||||
let expected_paths: Vec<String> = vec![
|
|
||||||
folder(dir.join("test_a")),
|
|
||||||
folder(dir.join("test_b")),
|
|
||||||
folder(dir.join("another")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Match the results
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ use nu_command::create_default_context;
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, ShellError, Span, Value,
|
PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs;
|
use nu_test_support::fs;
|
||||||
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
dir_str.push(SEP);
|
dir_str.push(SEP);
|
||||||
|
|
||||||
// Create a new engine with default context
|
// Create a new engine with default context
|
||||||
let mut engine_state = create_default_context(&dir);
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
// New stack
|
// New stack
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
// New delta state
|
|
||||||
let delta = StateDelta::new(&engine_state);
|
|
||||||
|
|
||||||
// Add pwd as env var
|
// Add pwd as env var
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
|
|
||||||
// match a list of suggestions with the expected values
|
// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
|
let expected_len = expected.len();
|
||||||
|
let suggestions_len = suggestions.len();
|
||||||
|
if expected_len != suggestions_len {
|
||||||
|
panic!(
|
||||||
|
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||||
|
Suggestions: {suggestions:#?} \n\
|
||||||
|
Expected: {expected:#?}\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
expected.iter().zip(suggestions).for_each(|it| {
|
expected.iter().zip(suggestions).for_each(|it| {
|
||||||
assert_eq!(it.0, &it.1.value);
|
assert_eq!(it.0, &it.1.value);
|
||||||
});
|
});
|
||||||
@ -97,6 +103,9 @@ pub fn merge_input(
|
|||||||
|
|
||||||
(block, working_set.render())
|
(block, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
assert!(eval_block(
|
assert!(eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -112,6 +121,6 @@ pub fn merge_input(
|
|||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Merge delta
|
// Merge environment into the permanent state
|
||||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
engine_state.merge_env(stack, &dir)
|
||||||
}
|
}
|
||||||
|
@ -1,88 +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.", 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);
|
|
||||||
}
|
|
@ -1,14 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Color configuration code used by Nushell"
|
description = "Color configuration code used by Nushell"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.64.0"
|
version = "0.69.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-json = { path = "../nu-json", version = "0.64.0" }
|
nu-json = { path = "../nu-json", version = "0.69.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.64.0" }
|
nu-table = { path = "../nu-table", version = "0.69.0" }
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
|
@ -11,12 +11,12 @@ pub struct NuStyle {
|
|||||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||||
// get the nu_ansi_term::Color foreground color
|
// get the nu_ansi_term::Color foreground color
|
||||||
let fg_color = match nu_style.fg {
|
let fg_color = match nu_style.fg {
|
||||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// get the nu_ansi_term::Color background color
|
// get the nu_ansi_term::Color background color
|
||||||
let bg_color = match nu_style.bg {
|
let bg_color = match nu_style.bg {
|
||||||
Some(bg) => color_from_hex(&bg).expect("error with background color"),
|
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// get the attributes
|
// get the attributes
|
||||||
|
@ -1,45 +1,49 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
description = "Nushell's built-in commands"
|
description = "Nushell's built-in commands"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.64.0"
|
version = "0.69.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.64.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.69.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.64.0" }
|
nu-engine = { path = "../nu-engine", version = "0.69.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.64.0" }
|
nu-glob = { path = "../nu-glob", version = "0.69.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.64.0" }
|
nu-json = { path = "../nu-json", version = "0.69.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.64.0" }
|
nu-parser = { path = "../nu-parser", version = "0.69.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.64.0" }
|
nu-path = { path = "../nu-path", version = "0.69.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.64.0" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.69.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.69.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.64.0" }
|
nu-system = { path = "../nu-system", version = "0.69.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.64.0" }
|
nu-table = { path = "../nu-table", version = "0.69.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.64.0" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.69.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.64.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.69.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.64.0" }
|
nu-utils = { path = "../nu-utils", version = "0.69.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
|
num-format = { version = "0.4.0" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
byteorder = "1.4.3"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
calamine = "0.18.0"
|
calamine = "0.18.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
chrono-tz = "0.6.1"
|
chrono-tz = "0.6.3"
|
||||||
crossterm = "0.23.0"
|
crossterm = "0.24.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
dialoguer = "0.9.0"
|
dialoguer = "0.9.0"
|
||||||
digest = "0.10.0"
|
digest = "0.10.0"
|
||||||
dtparse = "1.2.0"
|
dtparse = "1.2.0"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
encoding_rs = "0.8.30"
|
encoding_rs = "0.8.30"
|
||||||
|
fancy-regex = "0.10.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
filetime = "0.2.15"
|
filetime = "0.2.15"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
@ -51,42 +55,43 @@ is-root = "0.1.2"
|
|||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
lscolors = { version = "0.12.0", features = ["crossterm"]}
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
meval = "0.2.0"
|
meval = "0.2.0"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
num = { version = "0.4.0", optional = true }
|
num = { version = "0.4.0", optional = true }
|
||||||
|
num-traits = "0.2.14"
|
||||||
pathdiff = "0.2.1"
|
pathdiff = "0.2.1"
|
||||||
powierza-coefficient = "1.0"
|
powierza-coefficient = "1.0.1"
|
||||||
quick-xml = "0.22"
|
quick-xml = "0.23.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
regex = "1.5.4"
|
|
||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||||
roxmltree = "0.14.0"
|
roxmltree = "0.14.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
same-file = "1.0.6"
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8.16"
|
serde_yaml = "0.9.4"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
sysinfo = "0.23.5"
|
sysinfo = "0.26.2"
|
||||||
terminal_size = "0.1.17"
|
terminal_size = "0.2.1"
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.31"
|
||||||
titlecase = "1.1.0"
|
titlecase = "2.0.0"
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
unicode-segmentation = "1.8.0"
|
unicode-segmentation = "1.8.0"
|
||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
which = { version = "4.2.2", optional = true }
|
which = { version = "4.3.0", optional = true }
|
||||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
||||||
wax = { version = "0.4.0", features = ["diagnostics"] }
|
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "2.0.0"
|
umask = "2.0.0"
|
||||||
@ -97,15 +102,34 @@ version = "2.1.3"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
version = "0.21.1"
|
version = "0.23.2"
|
||||||
# path = "../../../../polars/polars"
|
|
||||||
optional = true
|
optional = true
|
||||||
features = [
|
features = [
|
||||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
"arg_where",
|
||||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
"checked_arithmetic",
|
||||||
"rolling_window", "strings", "rows", "random",
|
"concat_str",
|
||||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
"cross_join",
|
||||||
"dynamic_groupby"
|
"csv-file",
|
||||||
|
"cum_agg",
|
||||||
|
"default",
|
||||||
|
"dtype-datetime",
|
||||||
|
"dtype-struct",
|
||||||
|
"dtype-categorical",
|
||||||
|
"dynamic_groupby",
|
||||||
|
"ipc",
|
||||||
|
"is_in",
|
||||||
|
"json",
|
||||||
|
"lazy",
|
||||||
|
"object",
|
||||||
|
"parquet",
|
||||||
|
"random",
|
||||||
|
"rolling_window",
|
||||||
|
"rows",
|
||||||
|
"serde",
|
||||||
|
"serde-lazy",
|
||||||
|
"strings",
|
||||||
|
"strings",
|
||||||
|
"to_dummies",
|
||||||
]
|
]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
@ -125,10 +149,12 @@ dataframe = ["polars", "num"]
|
|||||||
database = ["sqlparser", "rusqlite"]
|
database = ["sqlparser", "rusqlite"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.11.0", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
|
proptest = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
@ -3,16 +3,18 @@ use std::process::Command;
|
|||||||
fn main() -> shadow_rs::SdResult<()> {
|
fn main() -> shadow_rs::SdResult<()> {
|
||||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
let hash = get_git_hash().unwrap_or_default();
|
||||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||||
|
|
||||||
shadow_rs::new()
|
shadow_rs::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
fn get_git_hash() -> Option<String> {
|
||||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
Command::new("git")
|
||||||
Ok(String::from_utf8(out.stdout)
|
.args(["rev-parse", "HEAD"])
|
||||||
.expect("could not convert stdout to string")
|
.output()
|
||||||
.trim()
|
.ok()
|
||||||
.to_string())
|
.filter(|output| output.status.success())
|
||||||
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||||
|
.map(|hash| hash.trim().to_string())
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
|
||||||
|
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
|
||||||
|
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
|
||||||
|
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
|
||||||
|
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
|
||||||
|
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
|
||||||
|
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
|
||||||
|
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
|
||||||
|
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
|
||||||
|
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
|
||||||
|
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
|
||||||
|
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
|
||||||
|
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
|
||||||
|
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
|
||||||
|
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
|
||||||
|
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
|
||||||
|
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"
|
100
crates/nu-command/src/bits/and.rs
Normal file
100
crates/nu-command/src/bits/and.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits and"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits and")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit and",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise and for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic and"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits and to two numbers",
|
||||||
|
example: "2 | bits and 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 2,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical and to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits and 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val & target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/bits/bits_.rs
Normal file
49
crates/nu-command/src/bits/bits_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_engine::get_full_help;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Bits;
|
||||||
|
|
||||||
|
impl Command for Bits {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits").category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Various commands for working with bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: get_full_help(&Bits.signature(), &Bits.examples(), engine_state, stack),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Bits;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Bits {})
|
||||||
|
}
|
||||||
|
}
|
99
crates/nu-command/src/bits/mod.rs
Normal file
99
crates/nu-command/src/bits/mod.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
mod and;
|
||||||
|
mod bits_;
|
||||||
|
mod not;
|
||||||
|
mod or;
|
||||||
|
mod rotate_left;
|
||||||
|
mod rotate_right;
|
||||||
|
mod shift_left;
|
||||||
|
mod shift_right;
|
||||||
|
mod xor;
|
||||||
|
|
||||||
|
use nu_protocol::Spanned;
|
||||||
|
|
||||||
|
pub use and::SubCommand as BitsAnd;
|
||||||
|
pub use bits_::Bits;
|
||||||
|
pub use not::SubCommand as BitsNot;
|
||||||
|
pub use or::SubCommand as BitsOr;
|
||||||
|
pub use rotate_left::SubCommand as BitsRotateLeft;
|
||||||
|
pub use rotate_right::SubCommand as BitsRotateRight;
|
||||||
|
pub use shift_left::SubCommand as BitsShiftLeft;
|
||||||
|
pub use shift_right::SubCommand as BitsShiftRight;
|
||||||
|
pub use xor::SubCommand as BitsXor;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum NumberBytes {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
Auto,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum InputNumType {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Four,
|
||||||
|
Eight,
|
||||||
|
SignedOne,
|
||||||
|
SignedTwo,
|
||||||
|
SignedFour,
|
||||||
|
SignedEight,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_number_bytes(number_bytes: &Option<Spanned<String>>) -> NumberBytes {
|
||||||
|
match number_bytes.as_ref() {
|
||||||
|
None => NumberBytes::Eight,
|
||||||
|
Some(size) => match size.item.as_str() {
|
||||||
|
"1" => NumberBytes::One,
|
||||||
|
"2" => NumberBytes::Two,
|
||||||
|
"4" => NumberBytes::Four,
|
||||||
|
"8" => NumberBytes::Eight,
|
||||||
|
"auto" => NumberBytes::Auto,
|
||||||
|
_ => NumberBytes::Invalid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> InputNumType {
|
||||||
|
if signed || val < 0 {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::SignedOne,
|
||||||
|
NumberBytes::Two => InputNumType::SignedTwo,
|
||||||
|
NumberBytes::Four => InputNumType::SignedFour,
|
||||||
|
NumberBytes::Eight => InputNumType::SignedEight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0x7F && val >= -(2i64.pow(7)) {
|
||||||
|
InputNumType::SignedOne
|
||||||
|
} else if val <= 0x7FFF && val >= -(2i64.pow(15)) {
|
||||||
|
InputNumType::SignedTwo
|
||||||
|
} else if val <= 0x7FFFFFFF && val >= -(2i64.pow(31)) {
|
||||||
|
InputNumType::SignedFour
|
||||||
|
} else {
|
||||||
|
InputNumType::SignedEight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::SignedFour,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match number_size {
|
||||||
|
NumberBytes::One => InputNumType::One,
|
||||||
|
NumberBytes::Two => InputNumType::Two,
|
||||||
|
NumberBytes::Four => InputNumType::Four,
|
||||||
|
NumberBytes::Eight => InputNumType::Eight,
|
||||||
|
NumberBytes::Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
InputNumType::One
|
||||||
|
} else if val <= 0xFFFF {
|
||||||
|
InputNumType::Two
|
||||||
|
} else if val <= 0xFFFFFFFF {
|
||||||
|
InputNumType::Four
|
||||||
|
} else {
|
||||||
|
InputNumType::Eight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberBytes::Invalid => InputNumType::Four,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
crates/nu-command/src/bits/not.rs
Normal file
163
crates/nu-command/src/bits/not.rs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
use super::{get_number_bytes, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits not"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits not")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs logical negation on each bit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["negation"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply logical negation to a list of numbers",
|
||||||
|
example: "[4 3 2] | bits not",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(140737488355323),
|
||||||
|
Value::test_int(140737488355324),
|
||||||
|
Value::test_int(140737488355325),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||||
|
example: "[4 3 2] | bits not -n 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(65531),
|
||||||
|
Value::test_int(65532),
|
||||||
|
Value::test_int(65533),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Apply logical negation to a list of numbers, treat input as signed number",
|
||||||
|
example: "[4 3 2] | bits not -s",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(-5),
|
||||||
|
Value::test_int(-4),
|
||||||
|
Value::test_int(-3),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
if signed || val < 0 {
|
||||||
|
Value::Int { val: !val, span }
|
||||||
|
} else {
|
||||||
|
use NumberBytes::*;
|
||||||
|
let out_val = match number_size {
|
||||||
|
One => !val & 0x00_00_00_00_00_FF,
|
||||||
|
Two => !val & 0x00_00_00_00_FF_FF,
|
||||||
|
Four => !val & 0x00_00_FF_FF_FF_FF,
|
||||||
|
Eight => !val & 0x7F_FF_FF_FF_FF_FF,
|
||||||
|
Auto => {
|
||||||
|
if val <= 0xFF {
|
||||||
|
!val & 0x00_00_00_00_00_FF
|
||||||
|
} else if val <= 0xFF_FF {
|
||||||
|
!val & 0x00_00_00_00_FF_FF
|
||||||
|
} else if val <= 0xFF_FF_FF_FF {
|
||||||
|
!val & 0x00_00_FF_FF_FF_FF
|
||||||
|
} else {
|
||||||
|
!val & 0x7F_FF_FF_FF_FF_FF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This case shouldn't happen here, as it's handled before
|
||||||
|
Invalid => 0,
|
||||||
|
};
|
||||||
|
Value::Int { val: out_val, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only numerical values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
100
crates/nu-command/src/bits/or.rs
Normal file
100
crates/nu-command/src/bits/or.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits or"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits or")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit or",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise or for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic or"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits or to two numbers",
|
||||||
|
example: "2 | bits or 6",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 6,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical or to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits or 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val | target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits rol"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits rol")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a number with 2 bits",
|
||||||
|
example: "17 | bits rol 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 68,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate left a list of numbers with 2 bits",
|
||||||
|
example: "[5 3 2] | bits rol 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_left(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_left(val as u8, bits, span),
|
||||||
|
Two => get_rotate_left(val as u16, bits, span),
|
||||||
|
Four => get_rotate_left(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::int::PrimInt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits ror"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits ror")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise rotate right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["rotate right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a number with 60 bits",
|
||||||
|
example: "17 | bits ror 60",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 272,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Rotate right a list of numbers of one byte",
|
||||||
|
example: "[15 33 92] | bits ror 2 -n 1",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(195),
|
||||||
|
Value::test_int(72),
|
||||||
|
Value::test_int(23),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
let rotate_result = i64::try_from(val.rotate_right(bits));
|
||||||
|
match rotate_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes rotate right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_rotate_right(val as u8, bits, span),
|
||||||
|
Two => get_rotate_right(val as u16, bits, span),
|
||||||
|
Four => get_rotate_right(val as u32, bits, span),
|
||||||
|
Eight => get_rotate_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_rotate_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_rotate_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_rotate_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_rotate_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
188
crates/nu-command/src/bits/shift_left.rs
Normal file
188
crates/nu-command/src/bits/shift_left.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShl;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shl"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shl")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift left for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift left"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number by 7 bits",
|
||||||
|
example: "2 | bits shl 7",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 256,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a number with 1 byte by 7 bits",
|
||||||
|
example: "2 | bits shl 7 -n 1",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a signed number by 1 bit",
|
||||||
|
example: "0x7F | bits shl 1 -s",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 254,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift left a list of numbers",
|
||||||
|
example: "[5 3 2] | bits shl 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shl(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift left {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift left failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift left {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_left(val as u8, bits, span),
|
||||||
|
Two => get_shift_left(val as u16, bits, span),
|
||||||
|
Four => get_shift_left(val as u32, bits, span),
|
||||||
|
Eight => get_shift_left(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_left(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_left(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_left(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
172
crates/nu-command/src/bits/shift_right.rs
Normal file
172
crates/nu-command/src/bits/shift_right.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use num_traits::CheckedShr;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits shr"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits shr")
|
||||||
|
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||||
|
.switch(
|
||||||
|
"signed",
|
||||||
|
"always treat input number as a signed number",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"number-bytes",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Bitwise shift right for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["shift right"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||||
|
let signed = call.has_flag("signed");
|
||||||
|
let number_bytes: Option<Spanned<String>> =
|
||||||
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
|
let bytes_len = get_number_bytes(&number_bytes);
|
||||||
|
if let NumberBytes::Invalid = bytes_len {
|
||||||
|
if let Some(val) = number_bytes {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
val.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Shift right a number with 2 bits",
|
||||||
|
example: "8 | bits shr 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 2,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Shift right a list of numbers",
|
||||||
|
example: "[15 35 2] | bits shr 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(3), Value::test_int(8), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||||
|
where
|
||||||
|
i64: std::convert::TryFrom<T>,
|
||||||
|
{
|
||||||
|
match val.checked_shr(bits) {
|
||||||
|
Some(val) => {
|
||||||
|
let shift_result = i64::try_from(val);
|
||||||
|
match shift_result {
|
||||||
|
Ok(val) => Value::Int { val, span },
|
||||||
|
Err(_) => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} of the specified number of bytes shift right {} bits exceed limit",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"Shift right failed".to_string(),
|
||||||
|
format!(
|
||||||
|
"{} shift right {} bits failed, you may shift too many bits",
|
||||||
|
val, bits
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => {
|
||||||
|
use InputNumType::*;
|
||||||
|
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||||
|
let bits = bits as u32;
|
||||||
|
let input_type = get_input_num_type(val, signed, number_size);
|
||||||
|
match input_type {
|
||||||
|
One => get_shift_right(val as u8, bits, span),
|
||||||
|
Two => get_shift_right(val as u16, bits, span),
|
||||||
|
Four => get_shift_right(val as u32, bits, span),
|
||||||
|
Eight => get_shift_right(val as u64, bits, span),
|
||||||
|
SignedOne => get_shift_right(val as i8, bits, span),
|
||||||
|
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||||
|
SignedFour => get_shift_right(val as i32, bits, span),
|
||||||
|
SignedEight => get_shift_right(val as i64, bits, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
100
crates/nu-command/src/bits/xor.rs
Normal file
100
crates/nu-command/src/bits/xor.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bits xor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bits xor")
|
||||||
|
.required(
|
||||||
|
"target",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"target integer to perform bit xor",
|
||||||
|
)
|
||||||
|
.category(Category::Bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Performs bitwise xor for integers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["logic xor"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |value| operate(value, target, head),
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply bits xor to two numbers",
|
||||||
|
example: "2 | bits xor 2",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply logical xor to a list of numbers",
|
||||||
|
example: "[8 3 2] | bits xor 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Int { val, span } => Value::Int {
|
||||||
|
val: val ^ target,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Only integer values are supported, input type: {:?}",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
other.span().unwrap_or(head),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
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;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter() {
|
for (val, count) in counter.into_iter() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||||
|
@ -29,7 +29,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "binary", "bytes", "bin"]
|
vec!["convert", "bytes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
@ -7,9 +8,6 @@ use nu_protocol::{
|
|||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generate_strftime_list;
|
|
||||||
use crate::parse_date_from_string;
|
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
timezone: Option<Spanned<String>>,
|
timezone: Option<Spanned<String>>,
|
||||||
offset: Option<Spanned<i64>>,
|
offset: Option<Spanned<i64>>,
|
||||||
@ -105,7 +103,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "date", "time", "timezone", "UTC"]
|
vec!["convert", "timezone", "UTC"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -148,6 +146,15 @@ impl Command for SubCommand {
|
|||||||
example: "1614434140 | into datetime -o +9",
|
example: "1614434140 | into datetime -o +9",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Convert timestamps like the sqlite history t",
|
||||||
|
example: "1656165681720 | into datetime",
|
||||||
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp_millis(1656165681720).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,10 +258,19 @@ fn action(
|
|||||||
|
|
||||||
return match timezone {
|
return match timezone {
|
||||||
// default to UTC
|
// default to UTC
|
||||||
None => Value::Date {
|
None => {
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
// be able to convert chrono::Utc::now()
|
||||||
span: head,
|
let dt = match ts.to_string().len() {
|
||||||
},
|
x if x > 13 => Utc.timestamp_nanos(ts).into(),
|
||||||
|
x if x > 10 => Utc.timestamp_millis(ts).into(),
|
||||||
|
_ => Utc.timestamp(ts, 0).into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Date {
|
||||||
|
val: dt,
|
||||||
|
span: head,
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(Spanned { item, span }) => match item {
|
Some(Spanned { item, span }) => match item {
|
||||||
Zone::Utc => Value::Date {
|
Zone::Utc => Value::Date {
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
val: Utc.timestamp(ts, 0).into(),
|
||||||
@ -295,7 +311,7 @@ fn action(
|
|||||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||||
Ok(d) => Value::Date { val: d, span: head },
|
Ok(d) => Value::Date { val: d, span: head },
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
return Value::Error {
|
Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
format!("could not parse as datetime using format '{}'", dt.0),
|
format!("could not parse as datetime using format '{}'", dt.0),
|
||||||
reason.to_string(),
|
reason.to_string(),
|
||||||
|
@ -42,7 +42,7 @@ impl Command for SubCommand {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer in table",
|
description: "Convert string to decimal in table",
|
||||||
example: "[[num]; ['5.01']] | into decimal num",
|
example: "[[num]; ['5.01']] | into decimal num",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![Value::Record {
|
vals: vec![Value::Record {
|
||||||
@ -54,15 +54,20 @@ impl Command for SubCommand {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer",
|
description: "Convert string to decimal",
|
||||||
example: "'1.345' | into decimal",
|
example: "'1.345' | into decimal",
|
||||||
result: Some(Value::test_float(1.345)),
|
result: Some(Value::test_float(1.345)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert decimal to integer",
|
description: "Convert decimal to decimal",
|
||||||
example: "'-5.9' | into decimal",
|
example: "'-5.9' | into decimal",
|
||||||
result: Some(Value::test_float(-5.9)),
|
result: Some(Value::test_float(-5.9)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert boolean to decimal",
|
||||||
|
example: "true | into decimal",
|
||||||
|
result: Some(Value::test_float(1.0)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,6 +123,13 @@ fn action(input: &Value, head: Span) -> Value {
|
|||||||
val: *v as f64,
|
val: *v as f64,
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
|
Value::Bool { val: b, span } => Value::Float {
|
||||||
|
val: match b {
|
||||||
|
true => 1.0,
|
||||||
|
false => 0.0,
|
||||||
|
},
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
other => {
|
other => {
|
||||||
let span = other.span();
|
let span = other.span();
|
||||||
match span {
|
match span {
|
||||||
|
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath, Expr},
|
ast::{Call, CellPath, Expr},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,6 +17,12 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
|
.named(
|
||||||
|
"convert",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"convert duration into another duration",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -28,6 +35,10 @@ impl Command for SubCommand {
|
|||||||
"Convert value to duration"
|
"Convert value to duration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"into duration does not take leap years into account and every month is calculated with 30 days"
|
||||||
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "time", "period"]
|
vec!["convert", "time", "period"]
|
||||||
}
|
}
|
||||||
@ -102,6 +113,15 @@ impl Command for SubCommand {
|
|||||||
span,
|
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,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
action(&v, head)
|
action(&v, &convert_to_unit, head)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
let r =
|
let d = convert_to_unit.clone();
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
let r = ret.update_cell_path(
|
||||||
|
&path.members,
|
||||||
|
Box::new(move |old| action(old, &d, head)),
|
||||||
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
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 Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
if let Expr::Int(x) = value.expr {
|
if let Expr::Int(x) = value.expr {
|
||||||
@ -155,21 +321,108 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ShellError::CantConvert(
|
Err(ShellError::CantConvertWithValue(
|
||||||
"duration".to_string(),
|
"duration".to_string(),
|
||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
span,
|
span,
|
||||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn string_to_unit_duration(
|
||||||
|
s: &str,
|
||||||
|
span: Span,
|
||||||
|
value_span: Span,
|
||||||
|
) -> Result<(&str, i64), ShellError> {
|
||||||
|
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||||
|
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||||
|
if let Expr::Int(x) = value.expr {
|
||||||
|
match unit.item {
|
||||||
|
Unit::Nanosecond => return Ok(("ns", x)),
|
||||||
|
Unit::Microsecond => return Ok(("us", x)),
|
||||||
|
Unit::Millisecond => return Ok(("ms", x)),
|
||||||
|
Unit::Second => return Ok(("sec", x)),
|
||||||
|
Unit::Minute => return Ok(("min", x)),
|
||||||
|
Unit::Hour => return Ok(("hr", x)),
|
||||||
|
Unit::Day => return Ok(("day", x)),
|
||||||
|
Unit::Week => return Ok(("wk", x)),
|
||||||
|
|
||||||
|
_ => return Ok(("ns", 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ShellError::CantConvertWithValue(
|
||||||
|
"duration".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
s.to_string(),
|
||||||
|
span,
|
||||||
|
value_span,
|
||||||
|
Some(
|
||||||
|
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration {
|
||||||
Value::String { val, .. } => match string_to_duration(val, span) {
|
val: _val_num,
|
||||||
Ok(val) => Value::Duration { val, span },
|
span: _value_span,
|
||||||
Err(error) => Value::Error { error },
|
} => {
|
||||||
},
|
if let Some(_to_unit) = convert_to_unit {
|
||||||
|
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 {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"'into duration' does not support this input".into(),
|
"'into duration' does not support this input".into(),
|
||||||
@ -195,8 +448,9 @@ mod test {
|
|||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let word = Value::test_string("3ns");
|
let word = Value::test_string("3ns");
|
||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,8 +462,9 @@ mod test {
|
|||||||
val: 4 * 1000,
|
val: 4 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,8 +476,9 @@ mod test {
|
|||||||
val: 5 * 1000 * 1000,
|
val: 5 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,8 +490,9 @@ mod test {
|
|||||||
val: 1000 * 1000 * 1000,
|
val: 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,8 +504,9 @@ mod test {
|
|||||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,8 +518,9 @@ mod test {
|
|||||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,8 +532,9 @@ mod test {
|
|||||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,8 +546,9 @@ mod test {
|
|||||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, span);
|
let actual = action(&word, &convert_duration, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "number", "size", "bytes"]
|
vec!["convert", "number", "bytes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
|
@ -8,6 +8,7 @@ use nu_protocol::{
|
|||||||
struct Arguments {
|
struct Arguments {
|
||||||
radix: Option<Value>,
|
radix: Option<Value>,
|
||||||
column_paths: Vec<CellPath>,
|
column_paths: Vec<CellPath>,
|
||||||
|
little_endian: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -21,6 +22,7 @@ impl Command for SubCommand {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into int")
|
Signature::build("into int")
|
||||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||||
|
.switch("little-endian", "use little-endian byte decoding", None)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -100,6 +102,21 @@ impl Command for SubCommand {
|
|||||||
example: "'FF' | into int -r 16",
|
example: "'FF' | into int -r 16",
|
||||||
result: Some(Value::test_int(255)),
|
result: Some(Value::test_int(255)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert octal string to integer",
|
||||||
|
example: "'0o10132' | into int",
|
||||||
|
result: Some(Value::test_int(4186)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert 0 padded string to integer",
|
||||||
|
example: "'0010132' | into int",
|
||||||
|
result: Some(Value::test_int(10132)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert 0 padded string to integer with radix",
|
||||||
|
example: "'0010132' | into int -r 8",
|
||||||
|
result: Some(Value::test_int(4186)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,6 +131,7 @@ fn into_int(
|
|||||||
|
|
||||||
let options = Arguments {
|
let options = Arguments {
|
||||||
radix: call.get_flag(engine_state, stack, "radix")?,
|
radix: call.get_flag(engine_state, stack, "radix")?,
|
||||||
|
little_endian: call.has_flag("little-endian"),
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
column_paths: call.rest(engine_state, stack, 0)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,13 +153,13 @@ fn into_int(
|
|||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if options.column_paths.is_empty() {
|
if options.column_paths.is_empty() {
|
||||||
action(&v, head, radix)
|
action(&v, head, radix, options.little_endian)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &options.column_paths {
|
for path in &options.column_paths {
|
||||||
let r = ret.update_cell_path(
|
let r = ret.update_cell_path(
|
||||||
&path.members,
|
&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 {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
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 {
|
match input {
|
||||||
Value::Int { val: _, .. } => {
|
Value::Int { val: _, .. } => {
|
||||||
if radix == 10 {
|
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::Filesize { val, .. } => Value::Int { val: *val, span },
|
||||||
Value::Float { val, .. } => Value::Int {
|
Value::Float { val, .. } => Value::Int {
|
||||||
val: *val as i64,
|
val: {
|
||||||
|
if radix == 10 {
|
||||||
|
*val as i64
|
||||||
|
} else {
|
||||||
|
match convert_int(
|
||||||
|
&Value::Int {
|
||||||
|
val: *val as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
radix,
|
||||||
|
)
|
||||||
|
.as_i64()
|
||||||
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
_ => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"Could not convert float to integer".to_string(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
@ -190,6 +233,33 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
|||||||
val: val.timestamp(),
|
val: val.timestamp(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Binary { val, span } => {
|
||||||
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
let mut val = val.to_vec();
|
||||||
|
|
||||||
|
if little_endian {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.push(0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
|
Value::Int {
|
||||||
|
val: LittleEndian::read_i64(&val),
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while val.len() < 8 {
|
||||||
|
val.insert(0, 0);
|
||||||
|
}
|
||||||
|
val.resize(8, 0);
|
||||||
|
|
||||||
|
Value::Int {
|
||||||
|
val: BigEndian::read_i64(&val),
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Value::Error {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
format!("'into int' for unsupported type '{}'", input.get_type()),
|
format!("'into int' for unsupported type '{}'", input.get_type()),
|
||||||
@ -203,11 +273,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
|||||||
let i = match input {
|
let i = match input {
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
if val.starts_with("0x") || val.starts_with("0b") {
|
let val = val.trim();
|
||||||
|
if val.starts_with("0x") // hex
|
||||||
|
|| val.starts_with("0b") // binary
|
||||||
|
|| val.starts_with("0o")
|
||||||
|
// octal
|
||||||
|
{
|
||||||
match int_from_string(val, head) {
|
match int_from_string(val, head) {
|
||||||
Ok(x) => return Value::Int { val: x, span: head },
|
Ok(x) => return Value::Int { val: x, span: head },
|
||||||
Err(e) => return Value::Error { error: e },
|
Err(e) => return Value::Error { error: e },
|
||||||
}
|
}
|
||||||
|
} else if val.starts_with("00") {
|
||||||
|
// It's a padded string
|
||||||
|
match i64::from_str_radix(val, radix) {
|
||||||
|
Ok(n) => return Value::Int { val: n, span: head },
|
||||||
|
Err(e) => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::CantConvert(
|
||||||
|
"string".to_string(),
|
||||||
|
"int".to_string(),
|
||||||
|
head,
|
||||||
|
Some(e.to_string()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val.to_string()
|
val.to_string()
|
||||||
}
|
}
|
||||||
@ -220,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 },
|
Ok(n) => Value::Int { val: n, span: head },
|
||||||
Err(_reason) => Value::Error {
|
Err(_reason) => Value::Error {
|
||||||
error: ShellError::CantConvert("int".to_string(), "string".to_string(), head, None),
|
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,7 +351,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
};
|
};
|
||||||
Ok(num)
|
Ok(num)
|
||||||
}
|
}
|
||||||
_ => match a_string.parse::<i64>() {
|
o if o.starts_with("0o") => {
|
||||||
|
let num = match i64::from_str_radix(o.trim_start_matches("0o"), 8) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_reason) => {
|
||||||
|
return Err(ShellError::CantConvert(
|
||||||
|
"int".to_string(),
|
||||||
|
"string".to_string(),
|
||||||
|
span,
|
||||||
|
Some(r#"octal digits following "0o" should be in 0-7"#.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(num)
|
||||||
|
}
|
||||||
|
_ => match trimmed.parse::<i64>() {
|
||||||
Ok(n) => Ok(n),
|
Ok(n) => Ok(n),
|
||||||
Err(_) => match a_string.parse::<f64>() {
|
Err(_) => match a_string.parse::<f64>() {
|
||||||
Ok(f) => Ok(f as i64),
|
Ok(f) => Ok(f as i64),
|
||||||
@ -269,7 +373,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
"int".to_string(),
|
"int".to_string(),
|
||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
span,
|
span,
|
||||||
None,
|
Some(format!(
|
||||||
|
r#"string "{}" does not represent a valid integer"#,
|
||||||
|
trimmed
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -294,21 +401,21 @@ mod test {
|
|||||||
let word = Value::test_string("10");
|
let word = Value::test_string("10");
|
||||||
let expected = Value::test_int(10);
|
let expected = Value::test_int(10);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data(), 10);
|
let actual = action(&word, Span::test_data(), 10, false);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_binary_to_integer() {
|
fn turns_binary_to_integer() {
|
||||||
let s = Value::test_string("0b101");
|
let s = Value::test_string("0b101");
|
||||||
let actual = action(&s, Span::test_data(), 10);
|
let actual = action(&s, Span::test_data(), 10, false);
|
||||||
assert_eq!(actual, Value::test_int(5));
|
assert_eq!(actual, Value::test_int(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_hex_to_integer() {
|
fn turns_hex_to_integer() {
|
||||||
let s = Value::test_string("0xFF");
|
let s = Value::test_string("0xFF");
|
||||||
let actual = action(&s, Span::test_data(), 16);
|
let actual = action(&s, Span::test_data(), 16, false);
|
||||||
assert_eq!(actual, Value::test_int(255));
|
assert_eq!(actual, Value::test_int(255));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +423,7 @@ mod test {
|
|||||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||||
let integer_str = Value::test_string("36anra");
|
let integer_str = Value::test_string("36anra");
|
||||||
|
|
||||||
let actual = action(&integer_str, Span::test_data(), 10);
|
let actual = action(&integer_str, Span::test_data(), 10, false);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error)
|
assert_eq!(actual.get_type(), Error)
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||||
SyntaxShape, Value,
|
Span, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::get_system_locale;
|
||||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
use num_format::ToFormattedString;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
@ -38,7 +38,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "str", "text"]
|
vec!["convert", "text"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -53,6 +53,14 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "convert integer to string and append three decimal places",
|
||||||
|
example: "5 | into string -d 3",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "5.000".to_string(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string and round to nearest integer",
|
description: "convert decimal to string and round to nearest integer",
|
||||||
example: "1.7 | into string -d 0",
|
example: "1.7 | into string -d 0",
|
||||||
@ -208,12 +216,8 @@ pub fn action(
|
|||||||
) -> Value {
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
let res = if group_digits {
|
let decimal_value = digits.unwrap_or(0) as usize;
|
||||||
format_int(*val) // int.to_formatted_string(*locale)
|
let res = format_int(*val, group_digits, decimal_value);
|
||||||
} else {
|
|
||||||
val.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::String { val: res, span }
|
Value::String { val: res, span }
|
||||||
}
|
}
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
@ -247,6 +251,15 @@ pub fn action(
|
|||||||
val: input.into_string(", ", config),
|
val: input.into_string(", ", config),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Error { error } => Value::String {
|
||||||
|
val: {
|
||||||
|
match into_code(error) {
|
||||||
|
Some(code) => code,
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
},
|
||||||
Value::Nothing { .. } => Value::String {
|
Value::Nothing { .. } => Value::String {
|
||||||
val: "".to_string(),
|
val: "".to_string(),
|
||||||
span,
|
span,
|
||||||
@ -279,21 +292,29 @@ pub fn action(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn format_int(int: i64) -> String {
|
|
||||||
int.to_string()
|
|
||||||
|
|
||||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
|
||||||
// #[cfg(windows)]
|
let locale = get_system_locale();
|
||||||
// {
|
|
||||||
// int.to_formatted_string(&Locale::en)
|
let str = if group_digits {
|
||||||
// }
|
int.to_formatted_string(&locale)
|
||||||
// #[cfg(not(windows))]
|
} else {
|
||||||
// {
|
int.to_string()
|
||||||
// match SystemLocale::default() {
|
};
|
||||||
// Ok(locale) => int.to_formatted_string(&locale),
|
|
||||||
// Err(_) => int.to_formatted_string(&Locale::en),
|
if decimals > 0 {
|
||||||
// }
|
let decimal_point = locale.decimal();
|
||||||
// }
|
|
||||||
|
format!(
|
||||||
|
"{}{decimal_point}{dummy:0<decimals$}",
|
||||||
|
str,
|
||||||
|
decimal_point = decimal_point,
|
||||||
|
dummy = "",
|
||||||
|
decimals = decimals
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
str
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -27,7 +27,7 @@ impl Command for Alias {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
77
crates/nu-command/src/core_commands/ast.rs
Normal file
77
crates/nu-command/src/core_commands/ast.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Ast;
|
||||||
|
|
||||||
|
impl Command for Ast {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ast"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Print the abstract syntax tree (ast) for a pipeline."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ast")
|
||||||
|
.required(
|
||||||
|
"pipeline",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the pipeline to print the ast for",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
|
||||||
|
eprintln!("output: {:#?}\nerror: {:#?}", output, err);
|
||||||
|
|
||||||
|
Ok(PipelineData::new(head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a string",
|
||||||
|
example: "ast 'hello'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a pipeline",
|
||||||
|
example: "ast 'ls | where name =~ README'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print the ast of a pipeline with an error",
|
||||||
|
example: "ast 'for x in 1..10 { echo $x '",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::Ast;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(Ast {})
|
||||||
|
}
|
||||||
|
}
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::ReplOperation;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::IntoPipelineData;
|
||||||
|
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commandline;
|
||||||
|
|
||||||
|
impl Command for Commandline {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("commandline")
|
||||||
|
.switch(
|
||||||
|
"append",
|
||||||
|
"appends the string to the end of the buffer",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"insert",
|
||||||
|
"inserts the string into the buffer at the cursor position",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"replace",
|
||||||
|
"replaces the current contents of the buffer (default)",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"cmd",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the string to perform the operation with",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"View or modify the current command line input buffer"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||||
|
let mut ops = engine_state
|
||||||
|
.repl_operation_queue
|
||||||
|
.lock()
|
||||||
|
.expect("repl op queue mutex");
|
||||||
|
ops.push_back(if call.has_flag("append") {
|
||||||
|
ReplOperation::Append(cmd.as_string()?)
|
||||||
|
} else if call.has_flag("insert") {
|
||||||
|
ReplOperation::Insert(cmd.as_string()?)
|
||||||
|
} else {
|
||||||
|
ReplOperation::Replace(cmd.as_string()?)
|
||||||
|
});
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
} else if let Some(ref cmd) = *engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex")
|
||||||
|
{
|
||||||
|
Ok(Value::String {
|
||||||
|
val: cmd.clone(),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ impl Command for Def {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -28,7 +28,33 @@ impl Command for DefEnv {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
=== EXTRA NOTE ===
|
||||||
|
All blocks are scoped, including variable definition and environment variable changes.
|
||||||
|
|
||||||
|
Because of this, the following doesn't work:
|
||||||
|
|
||||||
|
def-env cd_with_fallback [arg = ""] {
|
||||||
|
let fall_back_path = "/tmp"
|
||||||
|
if $arg != "" {
|
||||||
|
cd $arg
|
||||||
|
} else {
|
||||||
|
cd $fall_back_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instead, you have to use cd in the top level scope:
|
||||||
|
|
||||||
|
def-env cd_with_fallback [arg = ""] {
|
||||||
|
let fall_back_path = "/tmp"
|
||||||
|
let path = if $arg != "" {
|
||||||
|
$arg
|
||||||
|
} else {
|
||||||
|
$fall_back_path
|
||||||
|
}
|
||||||
|
cd $path
|
||||||
|
}"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use nu_engine::{eval_block, CallExt};
|
use nu_engine::{eval_block, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Do;
|
pub struct Do;
|
||||||
@ -23,6 +25,11 @@ impl Command for Do {
|
|||||||
"ignore errors as the block runs",
|
"ignore errors as the block runs",
|
||||||
Some('i'),
|
Some('i'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"capture-errors",
|
||||||
|
"capture errors as the block runs and return it",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
@ -37,6 +44,7 @@ impl Command for Do {
|
|||||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||||
let ignore_errors = call.has_flag("ignore-errors");
|
let ignore_errors = call.has_flag("ignore-errors");
|
||||||
|
let capture_errors = call.has_flag("capture-errors");
|
||||||
|
|
||||||
let mut stack = stack.captures_to_stack(&block.captures);
|
let mut stack = stack.captures_to_stack(&block.captures);
|
||||||
let block = engine_state.get_block(block.block_id);
|
let block = engine_state.get_block(block.block_id);
|
||||||
@ -85,7 +93,7 @@ impl Command for Do {
|
|||||||
block,
|
block,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
ignore_errors,
|
ignore_errors || capture_errors,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ignore_errors {
|
if ignore_errors {
|
||||||
@ -93,6 +101,11 @@ impl Command for Do {
|
|||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
Err(_) => Ok(PipelineData::new(call.head)),
|
Err(_) => Ok(PipelineData::new(call.head)),
|
||||||
}
|
}
|
||||||
|
} else if capture_errors {
|
||||||
|
match result {
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ impl Command for Echo {
|
|||||||
.category(Category::Core)
|
.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(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -2,8 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
Value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,7 +15,12 @@ impl Command for ErrorMake {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("error make")
|
Signature::build("error make")
|
||||||
.optional("error_struct", SyntaxShape::Record, "the error to create")
|
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||||
|
.switch(
|
||||||
|
"unspanned",
|
||||||
|
"remove the origin label from the error",
|
||||||
|
Some('u'),
|
||||||
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +29,7 @@ impl Command for ErrorMake {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["err", "panic", "crash", "throw"]
|
vec!["panic", "crash", "throw"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -33,42 +37,32 @@ impl Command for ErrorMake {
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||||
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
|
let unspanned = call.has_flag("unspanned");
|
||||||
|
|
||||||
if let Some(arg) = arg {
|
if unspanned {
|
||||||
Ok(make_error(&arg, span)
|
Err(make_error(&arg, None).unwrap_or_else(|| {
|
||||||
.map(|err| Value::Error { error: err })
|
ShellError::GenericError(
|
||||||
.unwrap_or_else(|| Value::Error {
|
"Creating error value not supported.".into(),
|
||||||
error: ShellError::GenericError(
|
"unsupported error format".into(),
|
||||||
"Creating error value not supported.".into(),
|
Some(span),
|
||||||
"unsupported error format".into(),
|
None,
|
||||||
Some(span),
|
Vec::new(),
|
||||||
None,
|
)
|
||||||
Vec::new(),
|
}))
|
||||||
),
|
|
||||||
})
|
|
||||||
.into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
input.map(
|
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
|
||||||
move |value| {
|
ShellError::GenericError(
|
||||||
make_error(&value, span)
|
"Creating error value not supported.".into(),
|
||||||
.map(|err| Value::Error { error: err })
|
"unsupported error format".into(),
|
||||||
.unwrap_or_else(|| Value::Error {
|
Some(span),
|
||||||
error: ShellError::GenericError(
|
None,
|
||||||
"Creating error value not supported.".into(),
|
Vec::new(),
|
||||||
"unsupported error format".into(),
|
)
|
||||||
Some(span),
|
}))
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
ctrlc,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +87,7 @@ impl Command for ErrorMake {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
||||||
if let Value::Record { .. } = &value {
|
if let Value::Record { .. } = &value {
|
||||||
let msg = value.get_data_by_key("msg");
|
let msg = value.get_data_by_key("msg");
|
||||||
let label = value.get_data_by_key("label");
|
let label = value.get_data_by_key("label");
|
||||||
@ -130,7 +124,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
|||||||
) => Some(ShellError::GenericError(
|
) => Some(ShellError::GenericError(
|
||||||
message,
|
message,
|
||||||
label_text,
|
label_text,
|
||||||
Some(throw_span),
|
throw_span,
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
@ -140,7 +134,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
|||||||
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
||||||
message,
|
message,
|
||||||
"originates from here".to_string(),
|
"originates from here".to_string(),
|
||||||
Some(throw_span),
|
throw_span,
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
|
@ -18,12 +18,12 @@ impl Command for ExportCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Export custom commands or environment variables from a module."
|
"Export definitions or environment variables from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -27,7 +27,7 @@ impl Command for ExportAlias {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -28,7 +28,7 @@ impl Command for ExportDef {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -28,7 +28,33 @@ impl Command for ExportDefEnv {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
=== EXTRA NOTE ===
|
||||||
|
All blocks are scoped, including variable definition and environment variable changes.
|
||||||
|
|
||||||
|
Because of this, the following doesn't work:
|
||||||
|
|
||||||
|
export def-env cd_with_fallback [arg = ""] {
|
||||||
|
let fall_back_path = "/tmp"
|
||||||
|
if $arg != "" {
|
||||||
|
cd $arg
|
||||||
|
} else {
|
||||||
|
cd $fall_back_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instead, you have to use cd in the top level scope:
|
||||||
|
|
||||||
|
export def-env cd_with_fallback [arg = ""] {
|
||||||
|
let fall_back_path = "/tmp"
|
||||||
|
let path = if $arg != "" {
|
||||||
|
$arg
|
||||||
|
} else {
|
||||||
|
$fall_back_path
|
||||||
|
}
|
||||||
|
cd $path
|
||||||
|
}"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -23,7 +23,7 @@ impl Command for ExportExtern {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -3,35 +3,26 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
|||||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportEnv;
|
pub struct ExportUse;
|
||||||
|
|
||||||
impl Command for ExportEnv {
|
impl Command for ExportUse {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"export env"
|
"export use"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
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 {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("export env")
|
Signature::build("export use")
|
||||||
.required(
|
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
||||||
"name",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"name of the environment variable",
|
|
||||||
)
|
|
||||||
.required(
|
|
||||||
"block",
|
|
||||||
SyntaxShape::Block(Some(vec![])),
|
|
||||||
"body of the environment variable definition",
|
|
||||||
)
|
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -45,16 +36,19 @@ impl Command for ExportEnv {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
//TODO: Add the env to stack
|
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::new(call.head))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Import and evaluate environment variable from a module",
|
description: "Re-export a command from another module",
|
||||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
|
module eggs { export use spam foo }
|
||||||
|
use eggs foo
|
||||||
|
foo
|
||||||
|
"#,
|
||||||
result: Some(Value::String {
|
result: Some(Value::String {
|
||||||
val: "BAZ".to_string(),
|
val: "foo".to_string(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
}),
|
}),
|
||||||
}]
|
}]
|
@ -23,7 +23,7 @@ impl Command for Extern {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -46,7 +46,7 @@ impl Command for For {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
@ -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::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
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;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Help;
|
pub struct Help;
|
||||||
|
|
||||||
@ -81,23 +84,29 @@ fn help(
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
let commands = engine_state.get_decl_ids_sorted(false);
|
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 {
|
if let Some(f) = find {
|
||||||
|
let org_search_string = f.item.clone();
|
||||||
let search_string = f.item.to_lowercase();
|
let search_string = f.item.to_lowercase();
|
||||||
let mut found_cmds_vec = Vec::new();
|
let mut found_cmds_vec = Vec::new();
|
||||||
|
|
||||||
for decl_id in commands {
|
for decl_id in commands {
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut vals = vec![];
|
let mut vals = vec![];
|
||||||
|
|
||||||
let decl = engine_state.get_decl(decl_id);
|
let decl = engine_state.get_decl(decl_id);
|
||||||
let sig = decl.signature().update_from_command(decl.borrow());
|
let sig = decl.signature().update_from_command(decl.borrow());
|
||||||
|
|
||||||
let key = sig.name;
|
let key = sig.name;
|
||||||
let usage = sig.usage;
|
let usage = sig.usage;
|
||||||
let search_terms = sig.search_terms;
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
let matches_term = if !search_terms.is_empty() {
|
let matches_term = if !search_terms.is_empty() {
|
||||||
search_terms
|
search_terms
|
||||||
.iter()
|
.iter()
|
||||||
@ -106,13 +115,16 @@ fn help(
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if key.to_lowercase().contains(&search_string)
|
let key_match = key.to_lowercase().contains(&search_string);
|
||||||
|| usage.to_lowercase().contains(&search_string)
|
let usage_match = usage.to_lowercase().contains(&search_string);
|
||||||
|| matches_term
|
if key_match || usage_match || matches_term {
|
||||||
{
|
|
||||||
cols.push("name".into());
|
cols.push("name".into());
|
||||||
vals.push(Value::String {
|
vals.push(Value::String {
|
||||||
val: key,
|
val: if key_match {
|
||||||
|
highlight_search_string(&key, &org_search_string, string_style)?
|
||||||
|
} else {
|
||||||
|
key
|
||||||
|
},
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,7 +142,7 @@ fn help(
|
|||||||
|
|
||||||
cols.push("is_custom".into());
|
cols.push("is_custom".into());
|
||||||
vals.push(Value::Bool {
|
vals.push(Value::Bool {
|
||||||
val: decl.get_block_id().is_some(),
|
val: decl.is_custom_command(),
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -142,7 +154,11 @@ fn help(
|
|||||||
|
|
||||||
cols.push("usage".into());
|
cols.push("usage".into());
|
||||||
vals.push(Value::String {
|
vals.push(Value::String {
|
||||||
val: usage,
|
val: if usage_match {
|
||||||
|
highlight_search_string(&usage, &org_search_string, string_style)?
|
||||||
|
} else {
|
||||||
|
usage
|
||||||
|
},
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -151,7 +167,30 @@ fn help(
|
|||||||
Value::nothing(head)
|
Value::nothing(head)
|
||||||
} else {
|
} else {
|
||||||
Value::String {
|
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,
|
span: head,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -204,7 +243,7 @@ fn help(
|
|||||||
|
|
||||||
cols.push("is_custom".into());
|
cols.push("is_custom".into());
|
||||||
vals.push(Value::Bool {
|
vals.push(Value::Bool {
|
||||||
val: decl.get_block_id().is_some(),
|
val: decl.is_custom_command(),
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,3 +342,70 @@ You can also learn more at https://www.nushell.sh/book/"#;
|
|||||||
.into_pipeline_data())
|
.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::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -19,14 +19,14 @@ impl Command for Hide {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Hide symbols in the current scope"
|
"Hide definitions in the current scope"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
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:
|
This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> 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),
|
expr: Expr::ImportPattern(pat),
|
||||||
..
|
..
|
||||||
}) = call.positional_nth(0)
|
}) = call.positional_nth(0)
|
||||||
{
|
{
|
||||||
pat
|
Spanned {
|
||||||
|
item: String::from_utf8_lossy(&pat.head.name).to_string(),
|
||||||
|
span: pat.head.span,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
"Unexpected import".into(),
|
"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()) {
|
stack.remove_env_var(engine_state, &env_var_name.item);
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::new(call.head))
|
||||||
}
|
}
|
||||||
|
75
crates/nu-command/src/core_commands/hide_env.rs
Normal file
75
crates/nu-command/src/core_commands/hide_env.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HideEnv;
|
||||||
|
|
||||||
|
impl Command for HideEnv {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hide-env"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("hide-env")
|
||||||
|
.rest(
|
||||||
|
"name",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"environment variable names to hide",
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"ignore-errors",
|
||||||
|
"do not throw an error if an environment variable was not found",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Hide environment variables in the current scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let ignore_errors = call.has_flag("ignore-errors");
|
||||||
|
|
||||||
|
for name in env_var_names {
|
||||||
|
if stack.remove_env_var(engine_state, &name.item).is_none() && !ignore_errors {
|
||||||
|
let all_names: Vec<String> = stack
|
||||||
|
.get_env_var_names(engine_state)
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
if let Some(closest_match) = did_you_mean(&all_names, &name.item) {
|
||||||
|
return Err(ShellError::DidYouMeanCustom(
|
||||||
|
format!("Environment variable '{}' not found", name.item),
|
||||||
|
closest_match,
|
||||||
|
name.span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PipelineData::new(call.head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Hide an environment variable",
|
||||||
|
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
||||||
|
result: Some(Value::boolean(false, Span::test_data())),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -1,131 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
|
|
||||||
Signature, Value,
|
|
||||||
};
|
|
||||||
use reedline::{
|
|
||||||
FileBackedHistory, History as ReedlineHistory, SearchDirection, SearchQuery,
|
|
||||||
SqliteBackedHistory,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct History;
|
|
||||||
|
|
||||||
impl Command for History {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"history"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Get the command history"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("history")
|
|
||||||
.switch("clear", "Clears out the history entries", Some('c'))
|
|
||||||
.category(Category::Core)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
|
||||||
if let Some(config_path) = nu_path::config_dir() {
|
|
||||||
let clear = call.has_flag("clear");
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
|
||||||
|
|
||||||
let mut history_path = config_path;
|
|
||||||
history_path.push("nushell");
|
|
||||||
match engine_state.config.history_file_format {
|
|
||||||
HistoryFileFormat::Sqlite => {
|
|
||||||
history_path.push("history.sqlite3");
|
|
||||||
}
|
|
||||||
HistoryFileFormat::PlainText => {
|
|
||||||
history_path.push("history.txt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clear {
|
|
||||||
let _ = std::fs::remove_file(history_path);
|
|
||||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
|
||||||
Ok(PipelineData::new(head))
|
|
||||||
} else {
|
|
||||||
let history_reader: Option<Box<dyn ReedlineHistory>> =
|
|
||||||
match engine_state.config.history_file_format {
|
|
||||||
HistoryFileFormat::Sqlite => SqliteBackedHistory::with_file(history_path)
|
|
||||||
.map(|inner| {
|
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
|
||||||
boxed
|
|
||||||
})
|
|
||||||
.ok(),
|
|
||||||
|
|
||||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
|
||||||
engine_state.config.max_history_size as usize,
|
|
||||||
history_path,
|
|
||||||
)
|
|
||||||
.map(|inner| {
|
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
|
||||||
boxed
|
|
||||||
})
|
|
||||||
.ok(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = history_reader
|
|
||||||
.and_then(|h| {
|
|
||||||
h.search(SearchQuery::everything(SearchDirection::Forward))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.map(move |entries| {
|
|
||||||
entries
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(move |(idx, entry)| Value::Record {
|
|
||||||
cols: vec!["command".to_string(), "index".to_string()],
|
|
||||||
vals: vec![
|
|
||||||
Value::String {
|
|
||||||
val: entry.command_line,
|
|
||||||
span: head,
|
|
||||||
},
|
|
||||||
Value::Int {
|
|
||||||
val: idx as i64,
|
|
||||||
span: head,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: head,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or(ShellError::FileNotFound(head))?
|
|
||||||
.into_pipeline_data(ctrlc);
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::FileNotFound(head))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
example: "history | length",
|
|
||||||
description: "Get current history length",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "history | last 5",
|
|
||||||
description: "Show last 5 commands you have ran",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
example: "history | wrap cmd | where cmd =~ cargo",
|
|
||||||
description: "Search all the commands from history that contains 'cargo'",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,7 +35,7 @@ impl Command for If {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -92,6 +92,7 @@ impl Command for If {
|
|||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
call.redirect_stderr,
|
call.redirect_stderr,
|
||||||
)
|
)
|
||||||
|
.map(|res| res.0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eval_expression_with_input(
|
eval_expression_with_input(
|
||||||
@ -102,6 +103,7 @@ impl Command for If {
|
|||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
call.redirect_stderr,
|
call.redirect_stderr,
|
||||||
)
|
)
|
||||||
|
.map(|res| res.0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::new(call.head))
|
||||||
|
@ -18,6 +18,10 @@ impl Command for Ignore {
|
|||||||
Signature::build("ignore").category(Category::Core)
|
Signature::build("ignore").category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["silent", "quiet", "out-null"]
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user