mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
294 Commits
Author | SHA1 | Date | |
---|---|---|---|
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba | |||
e76b3d61de | |||
1adebefc3e | |||
d1e1d0ac3e | |||
b398448cd9 | |||
02f92fa527 | |||
773d167449 | |||
aa92141ad7 | |||
80624267fd | |||
2030e25ddc | |||
247fff424d | |||
c902d8bc0c | |||
b0e5723a68 | |||
9273bb3f72 | |||
f7d3ccfc70 | |||
d86350af80 | |||
14512988ba | |||
33e1120add | |||
3278d290be | |||
daec3fc3d3 | |||
a6ba58ec41 | |||
65327e0e7e | |||
3ed3712fdc | |||
f46962d236 | |||
aa4778ff07 | |||
e81689f2c0 | |||
4656310a1c | |||
34e58bc5d6 | |||
fbe9d6f529 | |||
e266590813 | |||
b27148d14b | |||
4858a9a817 | |||
3ec53e544c | |||
c52d45cb97 | |||
11531b7630 | |||
a098a27837 | |||
2591bd8c63 | |||
a03fb946d9 | |||
9c58f2a522 | |||
3cb9147f22 | |||
f1d72e2670 | |||
f1e7a01b2e | |||
b88ace4cde | |||
34d7c17e78 | |||
3f1824111d | |||
fbae137442 | |||
9850424251 | |||
918ec9daa8 | |||
7b502a4c7f | |||
7b07e976b8 | |||
e45b169cba | |||
5ebfa10495 | |||
3f93dc2f1d | |||
a43514deb2 | |||
ab77bf3289 | |||
0afe1e4e67 | |||
ef26d539a7 | |||
fce8581321 | |||
ba6abd77c9 | |||
a7295c8f1b | |||
2a310ef187 | |||
530e250573 | |||
6fbc76bc0f | |||
884382bac4 | |||
d97975e9fa | |||
839b264261 | |||
7ef4e5f940 | |||
646aace05b | |||
772ad896c8 | |||
9c4bbe3c63 | |||
c5ca839294 | |||
5337a6dffa | |||
56ce10347e | |||
37bc90c62a | |||
ad7522bba0 | |||
bbcf374886 | |||
99c42582fe | |||
5a56d47f25 | |||
529c98085a | |||
2b955f82b7 | |||
1843fdc060 | |||
ec4e3a6d5c | |||
4ab468e65f | |||
1d18f6947e | |||
df3b6d9d26 | |||
4bbdb73668 | |||
62d3497bbb | |||
e614970c08 | |||
d931331b57 | |||
f18da2609a | |||
2ef9cc118e | |||
33674d3a98 | |||
0167649e6f | |||
cc263ee15d | |||
a4809f2e68 | |||
21770367e2 | |||
6145f734b7 | |||
9d8d305e9d | |||
eb55fd2383 | |||
613d2fb8df | |||
8783742060 | |||
20528e96c7 | |||
3b6c4c1bb5 | |||
cb18dd5200 | |||
ccebdd7a7f | |||
c3efb12733 | |||
d885258dc7 | |||
2da915d0c7 | |||
ae64c58f59 | |||
ff6868b329 | |||
47ef193600 | |||
c2f4969d4f | |||
08c98967e0 | |||
8d091f6f83 | |||
58094987ff | |||
ce26ef97e4 | |||
8f9bd4a299 | |||
45dd7d8770 | |||
0f10d984c3 | |||
2e5d981a09 | |||
c74254c2cb | |||
271fda7c91 | |||
0e5886ace1 | |||
4b89c5f900 | |||
dcab255d59 | |||
fc8512be39 | |||
e10ef4aaae | |||
0b70ca8451 | |||
555d9ee763 | |||
121b801baa | |||
9adcecbbf1 | |||
9f131d998d | |||
cd0a04f02a | |||
aaf5684f9c | |||
2f0cb044a5 | |||
8b55757a0b | |||
84fae6e07e | |||
63e220a763 | |||
a96fc21f88 | |||
1ba5b25b29 | |||
a871f2344a | |||
a217bc0715 | |||
34ab4d8360 | |||
48f1c3a49e | |||
692376e830 | |||
cc99df5ef1 | |||
d255a2a050 | |||
c07835f3ad | |||
78a5067434 | |||
cdeb8de75d | |||
606547ecb4 | |||
3b809b38e8 | |||
7c49a42b68 | |||
87823b0cb5 | |||
ebf845f431 | |||
ce6df93d05 | |||
e7958bebac | |||
233afebdf0 | |||
56069af42d | |||
376d22e331 | |||
7fc8ff60fd | |||
1f4791a191 | |||
2ac7a4d48d | |||
01386f4d58 | |||
1086fbe9b5 | |||
a83bd4ab20 | |||
26caf7e1b2 | |||
dd2a0e35f4 | |||
6a4eabf5c7 | |||
0e2c888f73 | |||
c140da5740 | |||
586c0ea3d8 | |||
d6f4189c7b | |||
7a820b1304 | |||
767201c40d | |||
3c3614a120 | |||
9e24e452a5 | |||
2cffff0c1b | |||
cf2e9cf481 | |||
6b2c7a4c86 | |||
98e199f7b5 | |||
10e463180e | |||
c9d0003818 | |||
e2a21afca8 | |||
2ea209bcc0 | |||
e049ca8ebf | |||
9037a9467b | |||
4c6cf36aa5 | |||
c92211c016 | |||
8bd6b5b913 | |||
b67fe31544 | |||
c8adb06ca7 | |||
9695331eed | |||
d42cfab6ef | |||
2b7c811402 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,7 +53,7 @@ body:
|
||||
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
||||
name: Feature Request
|
||||
description: "When you want a new feature for something that doesn't already exist"
|
||||
labels: "enhancement"
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
|
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Question
|
||||
description: "When you have a question to ask"
|
||||
labels: "question"
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Question
|
||||
description: Leave your question here
|
||||
placeholder: |
|
||||
A clear and concise question
|
||||
Example: Is there any equivalent of bash's $CDPATH in Nu?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context and details
|
||||
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
|
||||
validations:
|
||||
required: false
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -15,3 +15,7 @@ Make sure you've run and fixed any issues with these commands:
|
||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
||||
|
||||
# Documentation
|
||||
|
||||
- [ ] If your PR touches a user-facing nushell feature then make sure that there is an entry in the documentation (https://github.com/nushell/nushell.github.io) for the feature, and update it if necessary.
|
||||
|
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
@ -23,12 +23,13 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
# components: rustfmt, clippy
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
@ -74,17 +75,16 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
|
||||
# TODO: investigate why the cache was so big
|
||||
# - uses: Swatinem/rust-cache@v1
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
@ -111,11 +111,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
@ -161,11 +162,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# makes ci use rust-toolchain.toml
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: ${{ matrix.rust }}
|
||||
# override: true
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
|
41
.github/workflows/manual.yml
vendored
Normal file
41
.github/workflows/manual.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# This is a basic workflow that is manually triggered
|
||||
# Don't run it unless you know what you are doing
|
||||
|
||||
name: Manual Workflow for Winget Submission
|
||||
|
||||
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||
# or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Inputs the workflow accepts.
|
||||
inputs:
|
||||
ver:
|
||||
# Friendly description to be shown in the UI instead of 'ver'
|
||||
description: 'The nushell version to release'
|
||||
# Default value if no value is explicitly provided
|
||||
default: '0.66.0'
|
||||
# Input has to be provided for the workflow to run
|
||||
required: true
|
||||
uri:
|
||||
# Friendly description to be shown in the UI instead of 'uri'
|
||||
description: 'The nushell windows .msi package URI to publish'
|
||||
# Default value if no value is explicitly provided
|
||||
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
|
||||
# Input has to be provided for the workflow to run
|
||||
required: true
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job
|
||||
rls-winget-pkg:
|
||||
name: Publish winget package manually
|
||||
# The type of runner that the job will run on
|
||||
runs-on: windows-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs commands using the runners shell
|
||||
- name: Submit package to Windows Package Manager Community Repository Manually
|
||||
run: |
|
||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}
|
18
.github/workflows/release-pkg.nu
vendored
18
.github/workflows/release-pkg.nu
vendored
@ -41,7 +41,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
} else {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
sudo apt install musl-tools -y
|
||||
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
# Build for Windows without static-link-openssl feature
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in ['windows-latest'] {
|
||||
if ($flags | str trim | empty?) {
|
||||
if ($flags | str trim | is-empty) {
|
||||
cargo build --release --all --target $target --features=extra
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra $flags
|
||||
@ -76,11 +76,11 @@ cp -v README.release.txt $'($dist)/README.txt'
|
||||
|
||||
$'(char nl)Check binary release version detail:'; hr-line
|
||||
let ver = if $os == 'windows-latest' {
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
(do -i { ./output/nu -c 'version' }) | str join
|
||||
}
|
||||
if ($ver | str trim | empty?) {
|
||||
if ($ver | str trim | is-empty) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { $ver }
|
||||
|
||||
@ -113,7 +113,7 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
cd $src; hr-line
|
||||
# Wix need the binaries be stored in target/release/
|
||||
cp -r $'($dist)/*' target/release/
|
||||
cargo install cargo-wix --version 0.3.2
|
||||
cargo install cargo-wix --version 0.3.3
|
||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||
echo $'::set-output name=archive::($wixRelease)'
|
||||
|
||||
@ -124,14 +124,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
7z a $archive *
|
||||
print $'archive: ---> ($archive)';
|
||||
let pkg = (ls -f $archive | get name)
|
||||
if not ($pkg | empty?) {
|
||||
if not ($pkg | is-empty) {
|
||||
echo $'::set-output name=archive::($pkg | get 0)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def 'cargo-build-nu' [ options: string ] {
|
||||
if ($options | str trim | empty?) {
|
||||
if ($options | str trim | is-empty) {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||
@ -143,7 +143,7 @@ def 'hr-line' [
|
||||
--blank-line(-b): bool
|
||||
] {
|
||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||
if $blank-line { char nl }
|
||||
if $blank_line { char nl }
|
||||
}
|
||||
|
||||
# Get the specified env key's value or ''
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -70,9 +70,9 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v1
|
||||
uses: hustcer/setup-nu@v2.1
|
||||
with:
|
||||
version: 0.63.0
|
||||
version: 0.69.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
2
.github/workflows/winget-submission.yml
vendored
2
.github/workflows/winget-submission.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -29,3 +29,9 @@ debian/nu/
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
*.rsproj
|
||||
*.rsproj.user
|
||||
*.sln
|
@ -66,3 +66,9 @@ cargo build
|
||||
```shell
|
||||
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"
|
||||
```
|
||||
|
1276
Cargo.lock
generated
1276
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
63
Cargo.toml
63
Cargo.toml
@ -8,10 +8,9 @@ exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.66.0"
|
||||
version = "0.70.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -33,54 +32,61 @@ members = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossterm = "0.23.0"
|
||||
chrono = { version = "0.4.21", features = ["serde"] }
|
||||
crossterm = "0.24.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.66.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.66.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.66.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.66.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.66.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.66.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.66.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.66.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.66.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.66.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.66.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.66.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.66.0" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
pretty_env_logger = "0.4.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.70.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.70.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.70.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.70.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.70.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.70.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.70.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.70.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.70.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.70.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.70.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.70.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.70.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.70.0" }
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
rayon = "1.5.1"
|
||||
is_executable = "1.0.1"
|
||||
simplelog = "0.12.0"
|
||||
time = "0.3.12"
|
||||
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
# Our dependencies don't use OpenSSL on Windows
|
||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = "0.24"
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.66.0" }
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.70.0" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.8.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.15.0"
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
itertools = "0.10.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
default = ["plugin", "which-support", "trash-support"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
wasi = []
|
||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
@ -120,3 +126,8 @@ debug = false
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||
|
@ -71,7 +71,7 @@ Additionally, commands can output structured data (you can think of this as a th
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
||||
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
@ -1,3 +1,3 @@
|
||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||
|
||||
> register -e json ./nu_plugin_query
|
||||
> register ./nu_plugin_query
|
||||
|
@ -10,6 +10,7 @@ NU_PLUGINS=(
|
||||
'nu_plugin_gstat'
|
||||
'nu_plugin_inc'
|
||||
'nu_plugin_query'
|
||||
'nu_plugin_custom_values'
|
||||
)
|
||||
|
||||
echo "Building nushell"
|
||||
|
@ -24,9 +24,13 @@ cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_custom_values
|
||||
echo Building nu_plugin_custom_values.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
@ -11,6 +11,7 @@ let plugins = [
|
||||
nu_plugin_gstat,
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
|
@ -1,36 +1,40 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "CLI-related functionality for Nushell"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.66.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.66.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.66.0" }
|
||||
rstest = "0.15.0"
|
||||
nu-test-support = { path="../nu-test-support", version = "0.70.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.70.0" }
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.70.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.0" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
crossterm = "0.23.0"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
chrono = "0.4.19"
|
||||
atty = "0.2.14"
|
||||
chrono = "0.4.21"
|
||||
crossterm = "0.24.0"
|
||||
fancy-regex = "0.10.0"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
is_executable = "1.0.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
regex = "1.5.4"
|
||||
sysinfo = "0.24.1"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
percent-encoding = "2"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.26.2"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
}
|
||||
|
||||
impl CommandCompletion {
|
||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
||||
_: &StateWorkingSet,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
flattened,
|
||||
flat_shape,
|
||||
force_completion_after_space,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +119,8 @@ impl CommandCompletion {
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
let results = if find_externals {
|
||||
|
||||
if find_externals {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
@ -148,9 +152,7 @@ impl CommandCompletion {
|
||||
results
|
||||
} else {
|
||||
results
|
||||
};
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +209,10 @@ impl Completer for CommandCompletion {
|
||||
|| ((span.end - span.start) == 0)
|
||||
{
|
||||
// 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(
|
||||
working_set,
|
||||
span,
|
||||
|
@ -2,10 +2,11 @@ use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||
};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span,
|
||||
BlockId, PipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
@ -56,21 +57,101 @@ impl NuCompleter {
|
||||
suggestions
|
||||
}
|
||||
|
||||
fn external_completion(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
spans: &[String],
|
||||
offset: usize,
|
||||
span: Span,
|
||||
) -> Option<Vec<Suggestion>> {
|
||||
let stack = self.stack.clone();
|
||||
let block = self.engine_state.get_block(block_id);
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
// Line
|
||||
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
||||
if let Some(var_id) = pos_arg.var_id {
|
||||
callee_stack.add_var(
|
||||
var_id,
|
||||
Value::List {
|
||||
vals: spans
|
||||
.iter()
|
||||
.map(|it| Value::String {
|
||||
val: it.to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = eval_block(
|
||||
&self.engine_state,
|
||||
&mut callee_stack,
|
||||
block,
|
||||
PipelineData::new(span),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(pd) => {
|
||||
let value = pd.into_value(span);
|
||||
if let Value::List { vals, span: _ } = value {
|
||||
let result = map_value_completions(
|
||||
vals.iter(),
|
||||
Span {
|
||||
start: span.start,
|
||||
end: span.end,
|
||||
},
|
||||
offset,
|
||||
);
|
||||
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed to eval completer block: {}", err),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||
let initial_line = line.to_string();
|
||||
new_line.push(b'a');
|
||||
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||
new_line.insert(alias_total_offset + pos, b'a');
|
||||
let pos = offset + pos;
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
let mut spans: Vec<String> = vec![];
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
// Read the current spam to string
|
||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||
|
||||
// Skip the last 'a' as span item
|
||||
if flat_idx == flattened.len() - 1 {
|
||||
let mut chars = current_span_str.chars();
|
||||
chars.next_back();
|
||||
let current_span_str = chars.as_str().to_owned();
|
||||
spans.push(current_span_str.to_string());
|
||||
} else {
|
||||
spans.push(current_span_str.to_string());
|
||||
}
|
||||
|
||||
// Complete based on the last span
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
@ -89,9 +170,10 @@ impl NuCompleter {
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
let index = pos - (flat.0.start - span_offset);
|
||||
prefix.drain(index..);
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
@ -113,8 +195,42 @@ impl NuCompleter {
|
||||
|
||||
// Flags completion
|
||||
if prefix.starts_with(b"-") {
|
||||
let mut completer = FlagCompletion::new(expr);
|
||||
// Try to complete flag internally
|
||||
let mut completer = FlagCompletion::new(expr.clone());
|
||||
let result = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
if !result.is_empty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We got no results for internal completion
|
||||
// now we can check if external completer is set and use it
|
||||
if let Some(block_id) = config.external_completer {
|
||||
if let Some(external_result) =
|
||||
self.external_completion(block_id, &spans, offset, new_span)
|
||||
{
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// specially check if it is currently empty - always complete commands
|
||||
if flat_idx == 0 && working_set.get_span_contents(new_span).is_empty() {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
&working_set,
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
FlatShape::String,
|
||||
true,
|
||||
);
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
@ -125,7 +241,7 @@ impl NuCompleter {
|
||||
);
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source)
|
||||
// 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
|
||||
@ -133,7 +249,7 @@ impl NuCompleter {
|
||||
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" {
|
||||
if prev_expr_str == b"use" || prev_expr_str == b"source-env" {
|
||||
let mut completer =
|
||||
DotNuCompletion::new(self.engine_state.clone());
|
||||
|
||||
@ -212,9 +328,10 @@ impl NuCompleter {
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
flat_shape.clone(),
|
||||
false,
|
||||
);
|
||||
|
||||
let out: Vec<_> = self.process_completion(
|
||||
let mut out: Vec<_> = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
@ -223,11 +340,22 @@ impl NuCompleter {
|
||||
pos,
|
||||
);
|
||||
|
||||
if out.is_empty() {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
|
||||
return self.process_completion(
|
||||
// Try to complete using an external completer (if set)
|
||||
if let Some(block_id) = config.external_completer {
|
||||
if let Some(external_result) =
|
||||
self.external_completion(block_id, &spans, offset, new_span)
|
||||
{
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for file completion
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
out = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
@ -235,17 +363,18 @@ impl NuCompleter {
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +431,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push((&input[pos..]).to_owned());
|
||||
vec_names.push(input[pos..].to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
@ -383,3 +512,65 @@ fn most_left_variable(
|
||||
|
||||
Some((var, sublevels))
|
||||
}
|
||||
|
||||
pub fn map_value_completions<'a>(
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
list.filter_map(move |x| {
|
||||
// Match for string values
|
||||
if let Ok(s) = x.as_string() {
|
||||
return Some(Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Match for record values
|
||||
if let Ok((cols, vals)) = x.as_record() {
|
||||
let mut suggestion = Suggestion {
|
||||
value: String::from(""), // Initialize with empty string
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
};
|
||||
|
||||
// Iterate the cols looking for `value` and `description`
|
||||
cols.iter().zip(vals).for_each(|it| {
|
||||
// Match `value` column
|
||||
if it.0 == "value" {
|
||||
// Convert the value to string
|
||||
if let Ok(val_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.value = val_str;
|
||||
}
|
||||
}
|
||||
|
||||
// Match `description` column
|
||||
if it.0 == "description" {
|
||||
// Convert the value to string
|
||||
if let Ok(desc_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.description = Some(desc_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Some(suggestion);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::completer::map_value_completions;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
||||
sort_by: SortBy::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_completions<'a>(
|
||||
&self,
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
list.filter_map(move |x| {
|
||||
// Match for string values
|
||||
if let Ok(s) = x.as_string() {
|
||||
return Some(Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Match for record values
|
||||
if let Ok((cols, vals)) = x.as_record() {
|
||||
let mut suggestion = Suggestion {
|
||||
value: String::from(""), // Initialize with empty string
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
};
|
||||
|
||||
// Iterate the cols looking for `value` and `description`
|
||||
cols.iter().zip(vals).for_each(|it| {
|
||||
// Match `value` column
|
||||
if it.0 == "value" {
|
||||
// Convert the value to string
|
||||
if let Ok(val_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.value = val_str;
|
||||
}
|
||||
}
|
||||
|
||||
// Match `description` column
|
||||
if it.0 == "description" {
|
||||
// Convert the value to string
|
||||
if let Ok(desc_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.description = Some(desc_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Some(suggestion);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for CustomCompletion {
|
||||
@ -144,7 +83,7 @@ impl Completer for CustomCompletion {
|
||||
.and_then(|val| {
|
||||
val.as_list()
|
||||
.ok()
|
||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
||||
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
@ -189,7 +128,7 @@ impl Completer for CustomCompletion {
|
||||
|
||||
completions
|
||||
}
|
||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use {
|
||||
nu_ansi_term::{ansi::RESET, Style},
|
||||
reedline::{
|
||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion,
|
||||
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion, UndoBehavior,
|
||||
},
|
||||
};
|
||||
|
||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
||||
fn can_partially_complete(
|
||||
&mut self,
|
||||
_values_updated: bool,
|
||||
_line_buffer: &mut LineBuffer,
|
||||
_editor: &mut Editor,
|
||||
_completer: &mut dyn Completer,
|
||||
) -> bool {
|
||||
false
|
||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// Updates menu values
|
||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
||||
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
||||
if self.only_buffer_difference {
|
||||
if let Some(old_string) = &self.input {
|
||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
||||
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
||||
if !input.is_empty() {
|
||||
self.reset_position();
|
||||
self.values = completer.complete(input, start);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
||||
self.values =
|
||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
||||
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||
self.values = completer.complete(
|
||||
trimmed_buffer.as_str(),
|
||||
editor.line_buffer().insertion_point(),
|
||||
);
|
||||
self.reset_position();
|
||||
}
|
||||
}
|
||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
||||
/// collected from the completer
|
||||
fn update_working_details(
|
||||
&mut self,
|
||||
line_buffer: &mut LineBuffer,
|
||||
editor: &mut Editor,
|
||||
completer: &mut dyn Completer,
|
||||
painter: &Painter,
|
||||
) {
|
||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
||||
match event {
|
||||
MenuEvent::Activate(_) => {
|
||||
self.reset_position();
|
||||
self.input = Some(line_buffer.get_buffer().to_string());
|
||||
self.update_values(line_buffer, completer);
|
||||
self.input = Some(editor.get_buffer().to_string());
|
||||
self.update_values(editor, completer);
|
||||
}
|
||||
MenuEvent::Deactivate => self.active = false,
|
||||
MenuEvent::Edit(_) => {
|
||||
self.reset_position();
|
||||
self.update_values(line_buffer, completer);
|
||||
self.update_values(editor, completer);
|
||||
self.update_examples()
|
||||
}
|
||||
MenuEvent::NextElement => {
|
||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// The buffer gets replaced in the Span location
|
||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
||||
fn replace_in_buffer(&self, editor: &mut Editor) {
|
||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||
let start = span.start.min(line_buffer.len());
|
||||
let end = span.end.min(line_buffer.len());
|
||||
let start = span.start.min(editor.line_buffer().len());
|
||||
let end = span.end.min(editor.line_buffer().len());
|
||||
|
||||
let string_len = if let Some(example_index) = self.example_index {
|
||||
let example = self
|
||||
.examples
|
||||
let replacement = if let Some(example_index) = self.example_index {
|
||||
self.examples
|
||||
.get(example_index)
|
||||
.expect("the example index is always checked");
|
||||
|
||||
line_buffer.replace(start..end, example);
|
||||
example.len()
|
||||
.expect("the example index is always checked")
|
||||
} else {
|
||||
line_buffer.replace(start..end, &value);
|
||||
value.len()
|
||||
&value
|
||||
};
|
||||
|
||||
let mut offset = line_buffer.insertion_point();
|
||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||
line_buffer.set_insertion_point(offset);
|
||||
editor.edit_buffer(
|
||||
|lb| {
|
||||
lb.replace_range(start..end, replacement);
|
||||
let mut offset = lb.insertion_point();
|
||||
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||
lb.set_insertion_point(offset);
|
||||
},
|
||||
UndoBehavior::CreateUndoPoint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
@ -814,7 +815,6 @@ fn event_from_record(
|
||||
) -> Result<ReedlineEvent, ShellError> {
|
||||
let event = match name {
|
||||
"none" => ReedlineEvent::None,
|
||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||
@ -822,6 +822,8 @@ fn event_from_record(
|
||||
"ctrld" => ReedlineEvent::CtrlD,
|
||||
"ctrlc" => ReedlineEvent::CtrlC,
|
||||
"enter" => ReedlineEvent::Enter,
|
||||
"submit" => ReedlineEvent::Submit,
|
||||
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||
"esc" | "escape" => ReedlineEvent::Esc,
|
||||
"up" => ReedlineEvent::Up,
|
||||
"down" => ReedlineEvent::Down,
|
||||
|
@ -5,21 +5,26 @@ use crate::{
|
||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use fancy_regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{info, trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::{lex, parse};
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||
Spanned, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use regex::Regex;
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
sync::atomic::Ordering,
|
||||
time::Instant,
|
||||
};
|
||||
use strip_ansi_escapes::strip;
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
@ -37,9 +42,20 @@ pub fn evaluate_repl(
|
||||
stack: &mut Stack,
|
||||
nushell_path: &str,
|
||||
is_perf_true: bool,
|
||||
prerun_command: Option<Spanned<String>>,
|
||||
) -> Result<()> {
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
|
||||
// Guard against invocation without a connected terminal.
|
||||
// reedline / crossterm event polling will fail without a connected tty
|
||||
if !atty::is(atty::Stream::Stdin) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Nushell launched as interactive REPL but STDIN is not a TTY, either launch in a valid terminal or provide arguments to invoke a script!",
|
||||
))
|
||||
.into_diagnostic();
|
||||
}
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
@ -85,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 config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
let hist_sesh = match line_editor.get_history_session_id() {
|
||||
Some(id) => i64::from(id),
|
||||
None => 0,
|
||||
};
|
||||
engine_state.history_session_id = hist_sesh;
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
@ -119,6 +142,36 @@ pub fn evaluate_repl(
|
||||
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
let show_banner = config.show_banner;
|
||||
let use_ansi = config.use_ansi_coloring;
|
||||
if show_banner {
|
||||
let banner = get_banner(engine_state, stack);
|
||||
if use_ansi {
|
||||
println!("{}", banner);
|
||||
} else {
|
||||
let stripped_string = {
|
||||
if let Ok(bytes) = strip(&banner) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
banner
|
||||
}
|
||||
};
|
||||
|
||||
println!("{}", stripped_string);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.item.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
||||
}
|
||||
|
||||
loop {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
@ -289,13 +342,15 @@ pub fn evaluate_repl(
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = sys.host_name();
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = sys.host_name();
|
||||
c.hostname = hostname.clone();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
@ -303,6 +358,12 @@ pub fn evaluate_repl(
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
.replace(line_editor.current_buffer_contents().to_string());
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
@ -312,33 +373,20 @@ pub fn evaluate_repl(
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
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 tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
let path = nu_path::expand_path_with(&s, &cwd);
|
||||
|
||||
let orig = s.clone();
|
||||
let mut orig = s.clone();
|
||||
if orig.starts_with('`') {
|
||||
orig = trim_quotes_str(&orig).to_string()
|
||||
}
|
||||
|
||||
let path = nu_path::expand_path_with(&orig, &cwd);
|
||||
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
@ -391,10 +439,24 @@ pub fn evaluate_repl(
|
||||
0
|
||||
};
|
||||
|
||||
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
||||
let last_shell = if let Some(v) = last_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::String { val: path, span };
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
|
||||
} else {
|
||||
stack.add_env_var(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::Int {
|
||||
val: last_shell as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
} else if !s.trim().is_empty() {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
eval_source(
|
||||
@ -415,7 +477,8 @@ pub fn evaluate_repl(
|
||||
},
|
||||
);
|
||||
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||
{
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
@ -431,6 +494,21 @@ pub fn evaluate_repl(
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
|
||||
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]7;file://{}{}{}\x1b\\",
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||
percent_encoding::CONTROLS
|
||||
),
|
||||
if path.starts_with('/') { "" } else { "/" },
|
||||
percent_encoding::utf8_percent_encode(
|
||||
&path,
|
||||
percent_encoding::CONTROLS
|
||||
)
|
||||
))?;
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
@ -445,6 +523,25 @@ pub fn evaluate_repl(
|
||||
// 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) => {
|
||||
@ -465,6 +562,10 @@ pub fn evaluate_repl(
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
println!("Error: {:?}", err);
|
||||
// TODO: Identify possible error cases where a hard failure is preferable
|
||||
// Ignoring and reporting could hide bigger problems
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
@ -476,6 +577,113 @@ pub fn evaluate_repl(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||
let age = match eval_string_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
None,
|
||||
"(date now) - ('2019-05-10 09:59:12-0700' | 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{}
|
||||
|
||||
It's been this long since {}Nushell{}'s first commit:
|
||||
{}
|
||||
|
||||
{}You can disable this banner using the {}config nu{}{} command
|
||||
to modify the config.nu file and setting show_banner to false.
|
||||
|
||||
let-env config = {{
|
||||
show_banner: false
|
||||
...
|
||||
}}{}
|
||||
"#,
|
||||
"\x1b[32m", //start line 1 green
|
||||
"\x1b[32m", //start line 2
|
||||
"\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")
|
||||
@ -658,9 +866,6 @@ pub fn eval_hook(
|
||||
for var_id in var_ids.iter() {
|
||||
stack.vars.remove(var_id);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
@ -668,8 +873,6 @@ pub fn eval_hook(
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
@ -697,6 +900,9 @@ pub fn eval_hook(
|
||||
}
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -789,7 +995,7 @@ lazy_static! {
|
||||
fn looks_like_path(orig: &str) -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if DRIVE_PATH_REGEX.is_match(orig) {
|
||||
if DRIVE_PATH_REGEX.is_match(orig).unwrap_or(false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -231,24 +231,19 @@ pub fn eval_source(
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
match pipeline_data.print(engine_state, stack, false, false) {
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
Ok(exit_code) => {
|
||||
set_last_exit_code(stack, exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
|
@ -1,65 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[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)
|
||||
}
|
734
crates/nu-cli/tests/completions.rs
Normal file
734
crates/nu-cli/tests/completions.rs
Normal file
@ -0,0 +1,734 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use reedline::{Completer, Suggestion};
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{file, folder, match_suggestions, new_engine};
|
||||
|
||||
#[fixture]
|
||||
fn completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "def tst [--mod -s] {}";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn completer_strings() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_varialblecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "$ ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
assert_eq!(7, suggestions.len());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst --", 6);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||
// dbg!(&expected, &suggestions);
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-c ", 4);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||
mut completer_strings: NuCompleter,
|
||||
) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotnu_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test source completion
|
||||
let completion_str = "source-env ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
|
||||
// Test use completion
|
||||
let completion_str = "use ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn external_completer_trailing_space() {
|
||||
// https://github.com/nushell/nushell/issues/6378
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias ".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_completer_no_trailing_space() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(2, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_completer_pass_flags() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh api --".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_completions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cp {}", dir_str);
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("nushell")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
file(dir.join("custom_completion.nu")),
|
||||
file(dir.join(".hidden_file")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completions for a file
|
||||
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_ls_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "ls ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
fn command_open_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "open ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_rm_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "rm ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_cp_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "cp ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_save_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "save ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_touch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "touch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_watch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "watch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(14, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--directory".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--short-names".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
"-f".into(),
|
||||
"-h".into(),
|
||||
"-l".into(),
|
||||
"-s".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_directorycompletions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {}", dir_str);
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(9, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"config-path".into(),
|
||||
"env-path".into(),
|
||||
"history-path".into(),
|
||||
"home-path".into(),
|
||||
"loginshell-path".into(),
|
||||
"os-info".into(),
|
||||
"pid".into(),
|
||||
"scope".into(),
|
||||
"temp-path".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.h (filter)
|
||||
let suggestions = completer.complete("$nu.h", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var
|
||||
let suggestions = completer.complete("$actor.", 7);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var (filtering)
|
||||
let suggestions = completer.complete("$actor.n", 8);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.T", 6);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_command_and_flags() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_basic_command() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls "#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_another_alias() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -la"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||
// Create the second alias
|
||||
let alias = r#"alias lf = ll -f"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("lf t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||
let (_, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
|
||||
assert!(err.is_none());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
assert!(engine_state.merge_delta(delta).is_ok());
|
||||
|
||||
// Merge environment into the permanent state
|
||||
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||
|
||||
let latest_block_id = engine_state.num_blocks() - 1;
|
||||
|
||||
// Change config adding the external completer
|
||||
let mut config = engine_state.get_config().clone();
|
||||
config.external_completer = Some(latest_block_id);
|
||||
engine_state.set_config(&config);
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||
|
||||
completer.complete(input, input.len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "thiscommanddoesnotexist ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h | ls", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filecompletions_triggers_after_cursor() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("cp test_c", 3);
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{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)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_double_dash_argument(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_completions_single_dash_argument(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_completions_command(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_completions_subcommands(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_completions_subcommands_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);
|
||||
}
|
@ -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,272 +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 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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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_completion() {
|
||||
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)
|
||||
}
|
@ -1,38 +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!(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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -104,9 +104,7 @@ pub fn merge_input(
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
return Err(err);
|
||||
}
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
|
@ -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]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Color configuration code used by Nushell"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-config"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.66.0"
|
||||
version = "0.70.0"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.66.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.70.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.70.0" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
|
@ -11,12 +11,12 @@ pub struct NuStyle {
|
||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||
// get the nu_ansi_term::Color foreground color
|
||||
let fg_color = match nu_style.fg {
|
||||
Some(fg) => color_from_hex(&fg).expect("error with foreground color"),
|
||||
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
|
||||
_ => None,
|
||||
};
|
||||
// get the nu_ansi_term::Color background color
|
||||
let bg_color = match nu_style.bg {
|
||||
Some(bg) => color_from_hex(&bg).expect("error with background color"),
|
||||
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
|
||||
_ => None,
|
||||
};
|
||||
// get the attributes
|
||||
|
@ -1,29 +1,31 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Nushell's built-in commands"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.66.0"
|
||||
version = "0.70.0"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.66.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.66.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.66.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.66.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.66.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.66.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.70.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.70.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.70.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.70.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.70.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.70.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.70.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.70.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.70.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.70.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.70.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.70.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.70.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
num-format = { version = "0.4.3" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
alphanumeric-sort = "1.4.4"
|
||||
@ -31,16 +33,17 @@ base64 = "0.13.0"
|
||||
byteorder = "1.4.3"
|
||||
bytesize = "1.1.0"
|
||||
calamine = "0.18.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
|
||||
chrono-humanize = "0.2.1"
|
||||
chrono-tz = "0.6.1"
|
||||
crossterm = "0.23.0"
|
||||
chrono-tz = "0.6.3"
|
||||
crossterm = "0.24.0"
|
||||
csv = "1.1.6"
|
||||
dialoguer = "0.9.0"
|
||||
digest = "0.10.0"
|
||||
dtparse = "1.2.0"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.30"
|
||||
fancy-regex = "0.10.0"
|
||||
filesize = "0.2.0"
|
||||
filetime = "0.2.15"
|
||||
fs_extra = "1.2.0"
|
||||
@ -52,30 +55,31 @@ is-root = "0.1.2"
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
lscolors = { version = "0.10.0", features = ["crossterm"]}
|
||||
lscolors = { version = "0.12.0", features = ["crossterm"]}
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
meval = "0.2.0"
|
||||
mime = "0.3.16"
|
||||
notify = "4.0.17"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
num-traits = "0.2.14"
|
||||
pathdiff = "0.2.1"
|
||||
powierza-coefficient = "1.0.1"
|
||||
quick-xml = "0.23.0"
|
||||
rand = "0.8"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "6.3.0"
|
||||
same-file = "1.0.6"
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_ini = "0.2.0"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
serde_yaml = "0.9.4"
|
||||
sha2 = "0.10.0"
|
||||
# Disable default features b/c the default features build Git (very slow to compile)
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.24.6"
|
||||
sysinfo = "0.26.2"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
@ -83,11 +87,14 @@ toml = "0.5.8"
|
||||
unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
which = { version = "4.2.2", optional = true }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
which = { version = "4.3.0", optional = true }
|
||||
reedline = { version = "0.13.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
||||
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.10.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "2.0.0"
|
||||
@ -98,15 +105,34 @@ version = "2.1.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.polars]
|
||||
version = "0.22.8"
|
||||
# path = "../../../../polars/polars"
|
||||
version = "0.23.2"
|
||||
optional = true
|
||||
features = [
|
||||
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
|
||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
||||
"rolling_window", "strings", "rows", "random",
|
||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
||||
"dynamic_groupby", "dtype-categorical", "concat_str"
|
||||
"arg_where",
|
||||
"checked_arithmetic",
|
||||
"concat_str",
|
||||
"cross_join",
|
||||
"csv-file",
|
||||
"cum_agg",
|
||||
"default",
|
||||
"dtype-datetime",
|
||||
"dtype-struct",
|
||||
"dtype-categorical",
|
||||
"dynamic_groupby",
|
||||
"ipc",
|
||||
"is_in",
|
||||
"json",
|
||||
"lazy",
|
||||
"object",
|
||||
"parquet",
|
||||
"random",
|
||||
"rolling_window",
|
||||
"rows",
|
||||
"serde",
|
||||
"serde-lazy",
|
||||
"strings",
|
||||
"strings",
|
||||
"to_dummies",
|
||||
]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
@ -131,6 +157,7 @@ shadow-rs = { version = "0.16.1", default-features = false }
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
proptest = "1.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
rstest = "0.15.0"
|
||||
rstest = {version = "0.15.0", default-features = false}
|
||||
|
@ -3,16 +3,18 @@ use std::process::Command;
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||
let hash = get_git_hash().expect("failed to get latest git commit hash");
|
||||
let hash = get_git_hash().unwrap_or_default();
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
||||
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
||||
fn get_git_hash() -> Result<String, std::io::Error> {
|
||||
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
|
||||
Ok(String::from_utf8(out.stdout)
|
||||
.expect("could not convert stdout to string")
|
||||
.trim()
|
||||
.to_string())
|
||||
fn get_git_hash() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|output| output.status.success())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|hash| hash.trim().to_string())
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
|
||||
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
|
||||
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
|
||||
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
|
||||
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
|
||||
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
|
||||
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
|
||||
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
|
||||
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
|
||||
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
|
||||
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
|
||||
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
|
||||
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
|
||||
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
|
||||
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
|
||||
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
|
||||
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"
|
100
crates/nu-command/src/bits/and.rs
Normal file
100
crates/nu-command/src/bits/and.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits and"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits and")
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::Int,
|
||||
"target integer to perform bit and",
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise and for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["logic and"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply bits and to two numbers",
|
||||
example: "2 | bits and 2",
|
||||
result: Some(Value::Int {
|
||||
val: 2,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical and to a list of numbers",
|
||||
example: "[4 3 2] | bits and 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => Value::Int {
|
||||
val: val & target,
|
||||
span,
|
||||
},
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
49
crates/nu-command/src/bits/bits_.rs
Normal file
49
crates/nu-command/src/bits/bits_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bits;
|
||||
|
||||
impl Command for Bits {
|
||||
fn name(&self) -> &str {
|
||||
"bits"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits").category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Various commands for working with bits"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(&Bits.signature(), &Bits.examples(), engine_state, stack),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Bits;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Bits {})
|
||||
}
|
||||
}
|
99
crates/nu-command/src/bits/mod.rs
Normal file
99
crates/nu-command/src/bits/mod.rs
Normal file
@ -0,0 +1,99 @@
|
||||
mod and;
|
||||
mod bits_;
|
||||
mod not;
|
||||
mod or;
|
||||
mod rotate_left;
|
||||
mod rotate_right;
|
||||
mod shift_left;
|
||||
mod shift_right;
|
||||
mod xor;
|
||||
|
||||
use nu_protocol::Spanned;
|
||||
|
||||
pub use and::SubCommand as BitsAnd;
|
||||
pub use bits_::Bits;
|
||||
pub use not::SubCommand as BitsNot;
|
||||
pub use or::SubCommand as BitsOr;
|
||||
pub use rotate_left::SubCommand as BitsRotateLeft;
|
||||
pub use rotate_right::SubCommand as BitsRotateRight;
|
||||
pub use shift_left::SubCommand as BitsShiftLeft;
|
||||
pub use shift_right::SubCommand as BitsShiftRight;
|
||||
pub use xor::SubCommand as BitsXor;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum NumberBytes {
|
||||
One,
|
||||
Two,
|
||||
Four,
|
||||
Eight,
|
||||
Auto,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum InputNumType {
|
||||
One,
|
||||
Two,
|
||||
Four,
|
||||
Eight,
|
||||
SignedOne,
|
||||
SignedTwo,
|
||||
SignedFour,
|
||||
SignedEight,
|
||||
}
|
||||
|
||||
fn get_number_bytes(number_bytes: &Option<Spanned<String>>) -> NumberBytes {
|
||||
match number_bytes.as_ref() {
|
||||
None => NumberBytes::Eight,
|
||||
Some(size) => match size.item.as_str() {
|
||||
"1" => NumberBytes::One,
|
||||
"2" => NumberBytes::Two,
|
||||
"4" => NumberBytes::Four,
|
||||
"8" => NumberBytes::Eight,
|
||||
"auto" => NumberBytes::Auto,
|
||||
_ => NumberBytes::Invalid,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> InputNumType {
|
||||
if signed || val < 0 {
|
||||
match number_size {
|
||||
NumberBytes::One => InputNumType::SignedOne,
|
||||
NumberBytes::Two => InputNumType::SignedTwo,
|
||||
NumberBytes::Four => InputNumType::SignedFour,
|
||||
NumberBytes::Eight => InputNumType::SignedEight,
|
||||
NumberBytes::Auto => {
|
||||
if val <= 0x7F && val >= -(2i64.pow(7)) {
|
||||
InputNumType::SignedOne
|
||||
} else if val <= 0x7FFF && val >= -(2i64.pow(15)) {
|
||||
InputNumType::SignedTwo
|
||||
} else if val <= 0x7FFFFFFF && val >= -(2i64.pow(31)) {
|
||||
InputNumType::SignedFour
|
||||
} else {
|
||||
InputNumType::SignedEight
|
||||
}
|
||||
}
|
||||
NumberBytes::Invalid => InputNumType::SignedFour,
|
||||
}
|
||||
} else {
|
||||
match number_size {
|
||||
NumberBytes::One => InputNumType::One,
|
||||
NumberBytes::Two => InputNumType::Two,
|
||||
NumberBytes::Four => InputNumType::Four,
|
||||
NumberBytes::Eight => InputNumType::Eight,
|
||||
NumberBytes::Auto => {
|
||||
if val <= 0xFF {
|
||||
InputNumType::One
|
||||
} else if val <= 0xFFFF {
|
||||
InputNumType::Two
|
||||
} else if val <= 0xFFFFFFFF {
|
||||
InputNumType::Four
|
||||
} else {
|
||||
InputNumType::Eight
|
||||
}
|
||||
}
|
||||
NumberBytes::Invalid => InputNumType::Four,
|
||||
}
|
||||
}
|
||||
}
|
163
crates/nu-command/src/bits/not.rs
Normal file
163
crates/nu-command/src/bits/not.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use super::{get_number_bytes, NumberBytes};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits not"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits not")
|
||||
.switch(
|
||||
"signed",
|
||||
"always treat input number as a signed number",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs logical negation on each bit"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["negation"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let signed = call.has_flag("signed");
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(&number_bytes);
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
val.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply logical negation to a list of numbers",
|
||||
example: "[4 3 2] | bits not",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_int(140737488355323),
|
||||
Value::test_int(140737488355324),
|
||||
Value::test_int(140737488355325),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||
example: "[4 3 2] | bits not -n 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_int(65531),
|
||||
Value::test_int(65532),
|
||||
Value::test_int(65533),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Apply logical negation to a list of numbers, treat input as signed number",
|
||||
example: "[4 3 2] | bits not -s",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_int(-5),
|
||||
Value::test_int(-4),
|
||||
Value::test_int(-3),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => {
|
||||
if signed || val < 0 {
|
||||
Value::Int { val: !val, span }
|
||||
} else {
|
||||
use NumberBytes::*;
|
||||
let out_val = match number_size {
|
||||
One => !val & 0x00_00_00_00_00_FF,
|
||||
Two => !val & 0x00_00_00_00_FF_FF,
|
||||
Four => !val & 0x00_00_FF_FF_FF_FF,
|
||||
Eight => !val & 0x7F_FF_FF_FF_FF_FF,
|
||||
Auto => {
|
||||
if val <= 0xFF {
|
||||
!val & 0x00_00_00_00_00_FF
|
||||
} else if val <= 0xFF_FF {
|
||||
!val & 0x00_00_00_00_FF_FF
|
||||
} else if val <= 0xFF_FF_FF_FF {
|
||||
!val & 0x00_00_FF_FF_FF_FF
|
||||
} else {
|
||||
!val & 0x7F_FF_FF_FF_FF_FF
|
||||
}
|
||||
}
|
||||
// This case shouldn't happen here, as it's handled before
|
||||
Invalid => 0,
|
||||
};
|
||||
Value::Int { val: out_val, span }
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only numerical values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
100
crates/nu-command/src/bits/or.rs
Normal file
100
crates/nu-command/src/bits/or.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits or"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits or")
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::Int,
|
||||
"target integer to perform bit or",
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise or for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["logic or"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply bits or to two numbers",
|
||||
example: "2 | bits or 6",
|
||||
result: Some(Value::Int {
|
||||
val: 6,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical or to a list of numbers",
|
||||
example: "[8 3 2] | bits or 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => Value::Int {
|
||||
val: val | target,
|
||||
span,
|
||||
},
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
156
crates/nu-command/src/bits/rotate_left.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use num_traits::int::PrimInt;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits rol"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits rol")
|
||||
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||
.switch(
|
||||
"signed",
|
||||
"always treat input number as a signed number",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise rotate left for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate left"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag("signed");
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(&number_bytes);
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
val.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Rotate left a number with 2 bits",
|
||||
example: "17 | bits rol 2",
|
||||
result: Some(Value::Int {
|
||||
val: 68,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate left a list of numbers with 2 bits",
|
||||
example: "[5 3 2] | bits rol 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
let rotate_result = i64::try_from(val.rotate_left(bits));
|
||||
match rotate_result {
|
||||
Ok(val) => Value::Int { val, span },
|
||||
Err(_) => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
||||
format!(
|
||||
"{} of the specified number of bytes rotate left {} bits exceed limit",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let bits = bits as u32;
|
||||
let input_type = get_input_num_type(val, signed, number_size);
|
||||
match input_type {
|
||||
One => get_rotate_left(val as u8, bits, span),
|
||||
Two => get_rotate_left(val as u16, bits, span),
|
||||
Four => get_rotate_left(val as u32, bits, span),
|
||||
Eight => get_rotate_left(val as u64, bits, span),
|
||||
SignedOne => get_rotate_left(val as i8, bits, span),
|
||||
SignedTwo => get_rotate_left(val as i16, bits, span),
|
||||
SignedFour => get_rotate_left(val as i32, bits, span),
|
||||
SignedEight => get_rotate_left(val as i64, bits, span),
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
160
crates/nu-command/src/bits/rotate_right.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use num_traits::int::PrimInt;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits ror"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits ror")
|
||||
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||
.switch(
|
||||
"signed",
|
||||
"always treat input number as a signed number",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise rotate right for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["rotate right"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag("signed");
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(&number_bytes);
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
val.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Rotate right a number with 60 bits",
|
||||
example: "17 | bits ror 60",
|
||||
result: Some(Value::Int {
|
||||
val: 272,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate right a list of numbers of one byte",
|
||||
example: "[15 33 92] | bits ror 2 -n 1",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::test_int(195),
|
||||
Value::test_int(72),
|
||||
Value::test_int(23),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
let rotate_result = i64::try_from(val.rotate_right(bits));
|
||||
match rotate_result {
|
||||
Ok(val) => Value::Int { val, span },
|
||||
Err(_) => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
||||
format!(
|
||||
"{} of the specified number of bytes rotate right {} bits exceed limit",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let bits = bits as u32;
|
||||
let input_type = get_input_num_type(val, signed, number_size);
|
||||
match input_type {
|
||||
One => get_rotate_right(val as u8, bits, span),
|
||||
Two => get_rotate_right(val as u16, bits, span),
|
||||
Four => get_rotate_right(val as u32, bits, span),
|
||||
Eight => get_rotate_right(val as u64, bits, span),
|
||||
SignedOne => get_rotate_right(val as i8, bits, span),
|
||||
SignedTwo => get_rotate_right(val as i16, bits, span),
|
||||
SignedFour => get_rotate_right(val as i32, bits, span),
|
||||
SignedEight => get_rotate_right(val as i64, bits, span),
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
188
crates/nu-command/src/bits/shift_left.rs
Normal file
188
crates/nu-command/src/bits/shift_left.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use num_traits::CheckedShl;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits shl"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits shl")
|
||||
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||
.switch(
|
||||
"signed",
|
||||
"always treat input number as a signed number",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise shift left for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["shift left"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag("signed");
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(&number_bytes);
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
val.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Shift left a number by 7 bits",
|
||||
example: "2 | bits shl 7",
|
||||
result: Some(Value::Int {
|
||||
val: 256,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a number with 1 byte by 7 bits",
|
||||
example: "2 | bits shl 7 -n 1",
|
||||
result: Some(Value::Int {
|
||||
val: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a signed number by 1 bit",
|
||||
example: "0x7F | bits shl 1 -s",
|
||||
result: Some(Value::Int {
|
||||
val: 254,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a list of numbers",
|
||||
example: "[5 3 2] | bits shl 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
match val.checked_shl(bits) {
|
||||
Some(val) => {
|
||||
let shift_result = i64::try_from(val);
|
||||
match shift_result {
|
||||
Ok(val) => Value::Int { val, span },
|
||||
Err(_) => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
||||
format!(
|
||||
"{} of the specified number of bytes shift left {} bits exceed limit",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Shift left failed".to_string(),
|
||||
format!(
|
||||
"{} shift left {} bits failed, you may shift too many bits",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let bits = bits as u32;
|
||||
let input_type = get_input_num_type(val, signed, number_size);
|
||||
match input_type {
|
||||
One => get_shift_left(val as u8, bits, span),
|
||||
Two => get_shift_left(val as u16, bits, span),
|
||||
Four => get_shift_left(val as u32, bits, span),
|
||||
Eight => get_shift_left(val as u64, bits, span),
|
||||
SignedOne => get_shift_left(val as i8, bits, span),
|
||||
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||
SignedFour => get_shift_left(val as i32, bits, span),
|
||||
SignedEight => get_shift_left(val as i64, bits, span),
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
172
crates/nu-command/src/bits/shift_right.rs
Normal file
172
crates/nu-command/src/bits/shift_right.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use num_traits::CheckedShr;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits shr"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits shr")
|
||||
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||
.switch(
|
||||
"signed",
|
||||
"always treat input number as a signed number",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise shift right for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["shift right"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag("signed");
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(&number_bytes);
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
val.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Shift right a number with 2 bits",
|
||||
example: "8 | bits shr 2",
|
||||
result: Some(Value::Int {
|
||||
val: 2,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Shift right a list of numbers",
|
||||
example: "[15 35 2] | bits shr 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(3), Value::test_int(8), Value::test_int(0)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
match val.checked_shr(bits) {
|
||||
Some(val) => {
|
||||
let shift_result = i64::try_from(val);
|
||||
match shift_result {
|
||||
Ok(val) => Value::Int { val, span },
|
||||
Err(_) => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
||||
format!(
|
||||
"{} of the specified number of bytes shift right {} bits exceed limit",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Shift right failed".to_string(),
|
||||
format!(
|
||||
"{} shift right {} bits failed, you may shift too many bits",
|
||||
val, bits
|
||||
),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let bits = bits as u32;
|
||||
let input_type = get_input_num_type(val, signed, number_size);
|
||||
match input_type {
|
||||
One => get_shift_right(val as u8, bits, span),
|
||||
Two => get_shift_right(val as u16, bits, span),
|
||||
Four => get_shift_right(val as u32, bits, span),
|
||||
Eight => get_shift_right(val as u64, bits, span),
|
||||
SignedOne => get_shift_right(val as i8, bits, span),
|
||||
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||
SignedFour => get_shift_right(val as i32, bits, span),
|
||||
SignedEight => get_shift_right(val as i64, bits, span),
|
||||
}
|
||||
}
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
100
crates/nu-command/src/bits/xor.rs
Normal file
100
crates/nu-command/src/bits/xor.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"bits xor"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bits xor")
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::Int,
|
||||
"target integer to perform bit xor",
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise xor for integers"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["logic xor"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply bits xor to two numbers",
|
||||
example: "2 | bits xor 2",
|
||||
result: Some(Value::Int {
|
||||
val: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical xor to a list of numbers",
|
||||
example: "[8 3 2] | bits xor 2",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
match value {
|
||||
Value::Int { val, span } => Value::Int {
|
||||
val: val ^ target,
|
||||
span,
|
||||
},
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Only integer values are supported, input type: {:?}",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(head),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ impl Command for BytesAdd {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"add specified bytes to the input"
|
||||
"Add specified bytes to the input"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -49,7 +49,7 @@ impl Command for BytesIndexOf {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search", "index"]
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -39,7 +39,7 @@ impl Command for BytesLen {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["len", "size", "count"]
|
||||
vec!["size", "count"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -41,7 +41,7 @@ impl Command for BytesRemove {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"remove bytes"
|
||||
"Remove bytes"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -215,8 +215,8 @@ fn histogram_impl(
|
||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||
for (val, count) in counter.into_iter() {
|
||||
let quantile = match calc_method {
|
||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
||||
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||
};
|
||||
|
||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||
|
@ -29,7 +29,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "binary", "bytes", "bin"]
|
||||
vec!["convert", "bytes"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -103,7 +103,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "date", "time", "timezone", "UTC"]
|
||||
vec!["convert", "timezone", "UTC"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -311,7 +311,7 @@ fn action(
|
||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::Date { val: d, span: head },
|
||||
Err(reason) => {
|
||||
return Value::Error {
|
||||
Value::Error {
|
||||
error: ShellError::CantConvert(
|
||||
format!("could not parse as datetime using format '{}'", dt.0),
|
||||
reason.to_string(),
|
||||
|
@ -42,7 +42,7 @@ impl Command for SubCommand {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert string to integer in table",
|
||||
description: "Convert string to decimal in table",
|
||||
example: "[[num]; ['5.01']] | into decimal num",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
@ -54,15 +54,20 @@ impl Command for SubCommand {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to integer",
|
||||
description: "Convert string to decimal",
|
||||
example: "'1.345' | into decimal",
|
||||
result: Some(Value::test_float(1.345)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert decimal to integer",
|
||||
description: "Convert decimal to decimal",
|
||||
example: "'-5.9' | into decimal",
|
||||
result: Some(Value::test_float(-5.9)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert boolean to decimal",
|
||||
example: "true | into decimal",
|
||||
result: Some(Value::test_float(1.0)),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -118,6 +123,13 @@ fn action(input: &Value, head: Span) -> Value {
|
||||
val: *v as f64,
|
||||
span: *span,
|
||||
},
|
||||
Value::Bool { val: b, span } => Value::Float {
|
||||
val: match b {
|
||||
true => 1.0,
|
||||
false => 0.0,
|
||||
},
|
||||
span: *span,
|
||||
},
|
||||
other => {
|
||||
let span = other.span();
|
||||
match span {
|
||||
|
@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath, Expr},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -16,6 +17,12 @@ impl Command for SubCommand {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into duration")
|
||||
.named(
|
||||
"convert",
|
||||
SyntaxShape::String,
|
||||
"convert duration into another duration",
|
||||
Some('c'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -106,6 +113,22 @@ impl Command for SubCommand {
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to the requested duration as a string",
|
||||
example: "'7min' | into duration --convert sec",
|
||||
result: Some(Value::String {
|
||||
val: "420 sec".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert duration to the requested duration as a string",
|
||||
example: "420sec | into duration --convert min",
|
||||
result: Some(Value::String {
|
||||
val: "7 min".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -117,17 +140,23 @@ fn into_duration(
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let config = engine_state.get_config();
|
||||
let float_precision = config.float_precision as usize;
|
||||
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, head)
|
||||
action(&v, &convert_to_unit, float_precision, head)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let r =
|
||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
||||
let d = convert_to_unit.clone();
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
}
|
||||
@ -140,6 +169,150 @@ fn into_duration(
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_str_from_unit_to_unit(
|
||||
val: i64,
|
||||
from_unit: &str,
|
||||
to_unit: &str,
|
||||
span: Span,
|
||||
value_span: Span,
|
||||
) -> Result<f64, ShellError> {
|
||||
match (from_unit, to_unit) {
|
||||
("ns", "ns") => Ok(val as f64),
|
||||
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ns", "dec") => {
|
||||
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||
}
|
||||
|
||||
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||
("us", "us") => Ok(val as f64),
|
||||
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||
("ms", "ms") => Ok(val as f64),
|
||||
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||
("sec", "sec") => Ok(val as f64),
|
||||
("sec", "min") => Ok(val as f64 / 60.0),
|
||||
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||
("min", "sec") => Ok(val as f64 * 60.0),
|
||||
("min", "min") => Ok(val as f64),
|
||||
("min", "hr") => Ok(val as f64 / 60.0),
|
||||
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||
("hr", "min") => Ok(val as f64 * 60.0),
|
||||
("hr", "hr") => Ok(val as f64),
|
||||
("hr", "day") => Ok(val as f64 / 24.0),
|
||||
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||
|
||||
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||
("day", "hr") => Ok(val as f64 * 24.0),
|
||||
("day", "day") => Ok(val as f64),
|
||||
("day", "wk") => Ok(val as f64 / 7.0),
|
||||
("day", "month") => Ok(val as f64 / 30.0),
|
||||
("day", "yr") => Ok(val as f64 / 365.0),
|
||||
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||
|
||||
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||
("wk", "day") => Ok(val as f64 * 7.0),
|
||||
("wk", "wk") => Ok(val as f64),
|
||||
("wk", "month") => Ok(val as f64 / 4.0),
|
||||
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||
|
||||
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||
("month", "day") => Ok(val as f64 * 30.0),
|
||||
("month", "wk") => Ok(val as f64 * 4.0),
|
||||
("month", "month") => Ok(val as f64),
|
||||
("month", "yr") => Ok(val as f64 / 12.0),
|
||||
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||
|
||||
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||
("yr", "day") => Ok(val as f64 * 365.0),
|
||||
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||
("yr", "month") => Ok(val as f64 * 12.0),
|
||||
("yr", "yr") => Ok(val as f64),
|
||||
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||
|
||||
_ => Err(ShellError::CantConvertWithValue(
|
||||
"string duration".to_string(),
|
||||
"string duration".to_string(),
|
||||
to_unit.to_string(),
|
||||
span,
|
||||
value_span,
|
||||
Some(
|
||||
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
|
||||
.to_string(),
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||
@ -153,9 +326,6 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
|
||||
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -168,20 +338,133 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
||||
s.to_string(),
|
||||
span,
|
||||
value_span,
|
||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
||||
Some(
|
||||
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn action(input: &Value, span: Span) -> Value {
|
||||
fn string_to_unit_duration(
|
||||
s: &str,
|
||||
span: Span,
|
||||
value_span: Span,
|
||||
) -> Result<(&str, i64), ShellError> {
|
||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||
if let Expr::Int(x) = value.expr {
|
||||
match unit.item {
|
||||
Unit::Nanosecond => return Ok(("ns", x)),
|
||||
Unit::Microsecond => return Ok(("us", x)),
|
||||
Unit::Millisecond => return Ok(("ms", x)),
|
||||
Unit::Second => return Ok(("sec", x)),
|
||||
Unit::Minute => return Ok(("min", x)),
|
||||
Unit::Hour => return Ok(("hr", x)),
|
||||
Unit::Day => return Ok(("day", x)),
|
||||
Unit::Week => return Ok(("wk", x)),
|
||||
|
||||
_ => return Ok(("ns", 0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShellError::CantConvertWithValue(
|
||||
"duration".to_string(),
|
||||
"string".to_string(),
|
||||
s.to_string(),
|
||||
span,
|
||||
value_span,
|
||||
Some(
|
||||
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
convert_to_unit: &Option<Spanned<String>>,
|
||||
float_precision: usize,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::Duration { .. } => input.clone(),
|
||||
Value::Duration {
|
||||
val: val_num,
|
||||
span: value_span,
|
||||
} => {
|
||||
if let Some(to_unit) = convert_to_unit {
|
||||
let from_unit = "ns";
|
||||
let duration = *val_num;
|
||||
match convert_str_from_unit_to_unit(
|
||||
duration,
|
||||
from_unit,
|
||||
&to_unit.item,
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => {
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
} else {
|
||||
input.clone()
|
||||
}
|
||||
}
|
||||
Value::String {
|
||||
val,
|
||||
span: value_span,
|
||||
} => match string_to_duration(val, span, *value_span) {
|
||||
} => {
|
||||
if let Some(to_unit) = convert_to_unit {
|
||||
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
|
||||
let from_unit = dur.0;
|
||||
let duration = dur.1;
|
||||
match convert_str_from_unit_to_unit(
|
||||
duration,
|
||||
from_unit,
|
||||
&to_unit.item,
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => {
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
}
|
||||
} else {
|
||||
Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"'into duration' does not support this string input".into(),
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match string_to_duration(val, span, *value_span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"'into duration' does not support this input".into(),
|
||||
@ -207,8 +490,9 @@ mod test {
|
||||
let span = Span::test_data();
|
||||
let word = Value::test_string("3ns");
|
||||
let expected = Value::Duration { val: 3, span };
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -220,8 +504,9 @@ mod test {
|
||||
val: 4 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -233,8 +518,9 @@ mod test {
|
||||
val: 5 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -246,8 +532,9 @@ mod test {
|
||||
val: 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -259,8 +546,9 @@ mod test {
|
||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -272,8 +560,9 @@ mod test {
|
||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -285,8 +574,9 @@ mod test {
|
||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -298,8 +588,9 @@ mod test {
|
||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, span);
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "number", "size", "bytes"]
|
||||
vec!["convert", "number", "bytes"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -5,8 +5,8 @@ use nu_protocol::{
|
||||
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
||||
use nu_utils::get_system_locale;
|
||||
use num_format::ToFormattedString;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -38,7 +38,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "str", "text"]
|
||||
vec!["convert", "text"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -216,21 +216,8 @@ pub fn action(
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
let res = if group_digits {
|
||||
format_int(*val) // int.to_formatted_string(*locale)
|
||||
} else if let Some(dig) = digits {
|
||||
let mut val_with_trailing_zeroes = val.to_string();
|
||||
if dig != 0 {
|
||||
val_with_trailing_zeroes.push('.');
|
||||
}
|
||||
for _ in 0..dig {
|
||||
val_with_trailing_zeroes.push('0');
|
||||
}
|
||||
val_with_trailing_zeroes
|
||||
} else {
|
||||
val.to_string()
|
||||
};
|
||||
|
||||
let decimal_value = digits.unwrap_or(0) as usize;
|
||||
let res = format_int(*val, group_digits, decimal_value);
|
||||
Value::String { val: res, span }
|
||||
}
|
||||
Value::Float { val, .. } => {
|
||||
@ -305,21 +292,29 @@ pub fn action(
|
||||
},
|
||||
}
|
||||
}
|
||||
fn format_int(int: i64) -> String {
|
||||
int.to_string()
|
||||
|
||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
||||
// #[cfg(windows)]
|
||||
// {
|
||||
// int.to_formatted_string(&Locale::en)
|
||||
// }
|
||||
// #[cfg(not(windows))]
|
||||
// {
|
||||
// match SystemLocale::default() {
|
||||
// Ok(locale) => int.to_formatted_string(&locale),
|
||||
// Err(_) => int.to_formatted_string(&Locale::en),
|
||||
// }
|
||||
// }
|
||||
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
|
||||
let locale = get_system_locale();
|
||||
|
||||
let str = if group_digits {
|
||||
int.to_formatted_string(&locale)
|
||||
} else {
|
||||
int.to_string()
|
||||
};
|
||||
|
||||
if decimals > 0 {
|
||||
let decimal_point = locale.decimal();
|
||||
|
||||
format!(
|
||||
"{}{decimal_point}{dummy:0<decimals$}",
|
||||
str,
|
||||
decimal_point = decimal_point,
|
||||
dummy = "",
|
||||
decimals = decimals
|
||||
)
|
||||
} else {
|
||||
str
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -27,7 +27,7 @@ impl Command for Alias {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
77
crates/nu-command/src/core_commands/ast.rs
Normal file
77
crates/nu-command/src/core_commands/ast.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ast;
|
||||
|
||||
impl Command for Ast {
|
||||
fn name(&self) -> &str {
|
||||
"ast"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the abstract syntax tree (ast) for a pipeline."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ast")
|
||||
.required(
|
||||
"pipeline",
|
||||
SyntaxShape::String,
|
||||
"the pipeline to print the ast for",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
|
||||
eprintln!("output: {:#?}\nerror: {:#?}", output, err);
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Print the ast of a string",
|
||||
example: "ast 'hello'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print the ast of a pipeline",
|
||||
example: "ast 'ls | where name =~ README'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print the ast of a pipeline with an error",
|
||||
example: "ast 'for x in 1..10 { echo $x '",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Ast;
|
||||
use crate::test_examples;
|
||||
test_examples(Ast {})
|
||||
}
|
||||
}
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::ReplOperation;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::IntoPipelineData;
|
||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Commandline;
|
||||
|
||||
impl Command for Commandline {
|
||||
fn name(&self) -> &str {
|
||||
"commandline"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("commandline")
|
||||
.switch(
|
||||
"append",
|
||||
"appends the string to the end of the buffer",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"insert",
|
||||
"inserts the string into the buffer at the cursor position",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"replace",
|
||||
"replaces the current contents of the buffer (default)",
|
||||
Some('r'),
|
||||
)
|
||||
.optional(
|
||||
"cmd",
|
||||
SyntaxShape::String,
|
||||
"the string to perform the operation with",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View or modify the current command line input buffer"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["repl", "interactive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
ops.push_back(if call.has_flag("append") {
|
||||
ReplOperation::Append(cmd.as_string()?)
|
||||
} else if call.has_flag("insert") {
|
||||
ReplOperation::Insert(cmd.as_string()?)
|
||||
} else {
|
||||
ReplOperation::Replace(cmd.as_string()?)
|
||||
});
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
} else if let Some(ref cmd) = *engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
{
|
||||
Ok(Value::String {
|
||||
val: cmd.clone(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ impl Command for Def {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -28,7 +28,7 @@ impl Command for DefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
|
@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -102,9 +103,75 @@ impl Command for Do {
|
||||
Err(_) => Ok(PipelineData::new(call.head)),
|
||||
}
|
||||
} else if capture_errors {
|
||||
// collect stdout and stderr and check exit code.
|
||||
// if exit code is not 0, return back ShellError.
|
||||
match result {
|
||||
Ok(x) => Ok(x),
|
||||
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code,
|
||||
span,
|
||||
metadata,
|
||||
}) => {
|
||||
// collect all output first.
|
||||
let mut stderr_ctrlc = None;
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => {
|
||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
stderr_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let mut stdout_ctrlc = None;
|
||||
let stdout_msg = match stdout {
|
||||
None => "".to_string(),
|
||||
Some(stdout_stream) => {
|
||||
stdout_ctrlc = stdout_stream.ctrlc.clone();
|
||||
stdout_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let mut exit_code_ctrlc = None;
|
||||
let exit_code: Vec<Value> = match exit_code {
|
||||
None => vec![],
|
||||
Some(exit_code_stream) => {
|
||||
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||
exit_code_stream.into_iter().collect()
|
||||
}
|
||||
};
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||
if *code != 0 {
|
||||
return Err(ShellError::ExternalCommand(
|
||||
"External command runs to failed".to_string(),
|
||||
stderr_msg,
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
// construct pipeline data to our caller
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
|
||||
stdout_ctrlc,
|
||||
span,
|
||||
)),
|
||||
stderr: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||
stderr_ctrlc,
|
||||
span,
|
||||
)),
|
||||
exit_code: Some(ListStream::from_stream(
|
||||
exit_code.into_iter(),
|
||||
exit_code_ctrlc,
|
||||
)),
|
||||
span,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
Ok(other) => Ok(other),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
result
|
||||
|
@ -29,7 +29,7 @@ impl Command for ErrorMake {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["err", "panic", "crash", "throw"]
|
||||
vec!["panic", "crash", "throw"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -18,12 +18,12 @@ impl Command for ExportCommand {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export custom commands or environment variables from a module."
|
||||
"Export definitions or environment variables from a module."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -59,4 +59,8 @@ impl Command for ExportCommand {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ impl Command for ExportAlias {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
@ -51,4 +51,8 @@ impl Command for ExportAlias {
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["aka", "abbr", "module"]
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl Command for ExportDef {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
@ -55,4 +55,8 @@ impl Command for ExportDef {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl Command for ExportDefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
@ -81,4 +81,8 @@ export def-env cd_with_fallback [arg = ""] {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ impl Command for ExportExtern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -47,4 +47,8 @@ impl Command for ExportExtern {
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["signature", "module", "declare"]
|
||||
}
|
||||
}
|
||||
|
@ -3,35 +3,26 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportEnv;
|
||||
pub struct ExportUse;
|
||||
|
||||
impl Command for ExportEnv {
|
||||
impl Command for ExportUse {
|
||||
fn name(&self) -> &str {
|
||||
"export env"
|
||||
"export use"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export a block from a module that will be evaluated as an environment variable when imported."
|
||||
"Use definitions from a module and export them from this module"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export env")
|
||||
.required(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"name of the environment variable",
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the environment variable definition",
|
||||
)
|
||||
Signature::build("export use")
|
||||
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -45,18 +36,25 @@ impl Command for ExportEnv {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
//TODO: Add the env to stack
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Import and evaluate environment variable from a module",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
description: "Re-export a command from another module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
module eggs { export use spam foo }
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
val: "foo".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["reexport", "import", "module"]
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ impl Command for Extern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -46,7 +46,7 @@ impl Command for For {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::{
|
||||
Color::{Default, Red, White},
|
||||
Style,
|
||||
@ -11,7 +12,6 @@ use nu_protocol::{
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Help;
|
||||
|
||||
@ -142,7 +142,7 @@ fn help(
|
||||
|
||||
cols.push("is_custom".into());
|
||||
vals.push(Value::Bool {
|
||||
val: decl.get_block_id().is_some(),
|
||||
val: decl.is_custom_command(),
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -243,7 +243,7 @@ fn help(
|
||||
|
||||
cols.push("is_custom".into());
|
||||
vals.push(Value::Bool {
|
||||
val: decl.get_block_id().is_some(),
|
||||
val: decl.is_custom_command(),
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -350,7 +350,7 @@ pub fn highlight_search_string(
|
||||
string_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){}", needle);
|
||||
let regex = match regex::Regex::new(®ex_string) {
|
||||
let regex = match Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
@ -362,28 +362,50 @@ pub fn highlight_search_string(
|
||||
));
|
||||
}
|
||||
};
|
||||
// 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(haystack) {
|
||||
let start = match cap.get(0) {
|
||||
Some(cap) => cap.start(),
|
||||
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 cap.get(0) {
|
||||
Some(cap) => cap.end(),
|
||||
let end = match capture.get(0) {
|
||||
Some(acap) => acap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&haystack[last_match_end..start])
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&haystack[start..end]).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(&haystack[last_match_end..]).to_string());
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -19,14 +19,14 @@ impl Command for Hide {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide symbols in the current scope"
|
||||
"Hide definitions in the current scope"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Symbols are hidden by priority: First aliases, then custom commands, then environment variables.
|
||||
r#"Definitions are hidden by priority: First aliases, then custom commands.
|
||||
|
||||
This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let import_pattern = if let Some(Expression {
|
||||
let env_var_name = if let Some(Expression {
|
||||
expr: Expr::ImportPattern(pat),
|
||||
..
|
||||
}) = call.positional_nth(0)
|
||||
{
|
||||
pat
|
||||
Spanned {
|
||||
item: String::from_utf8_lossy(&pat.head.name).to_string(),
|
||||
span: pat.head.span,
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::GenericError(
|
||||
"Unexpected import".into(),
|
||||
@ -56,78 +59,7 @@ This command is a parser keyword. For details, check:
|
||||
));
|
||||
};
|
||||
|
||||
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
||||
};
|
||||
|
||||
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
|
||||
// The first word is a module
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let env_vars_to_hide = if import_pattern.members.is_empty() {
|
||||
module.env_vars_with_head(&import_pattern.head.name)
|
||||
} else {
|
||||
match &import_pattern.members[0] {
|
||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
||||
ImportPatternMember::Name { name, span } => {
|
||||
let mut output = vec![];
|
||||
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
ImportPatternMember::List { names } => {
|
||||
let mut output = vec![];
|
||||
|
||||
for (name, span) in names {
|
||||
if let Some((name, id)) =
|
||||
module.env_var_with_head(name, &import_pattern.head.name)
|
||||
{
|
||||
output.push((name, id));
|
||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (name, _) in env_vars_to_hide {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.span()));
|
||||
};
|
||||
|
||||
if stack.remove_env_var(engine_state, &name).is_none() {
|
||||
return Err(ShellError::NotFound(
|
||||
call.positional_nth(0)
|
||||
.expect("already checked for present positional")
|
||||
.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
|
||||
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
|
||||
{
|
||||
// TODO: we may want to error in the future
|
||||
}
|
||||
stack.remove_env_var(engine_state, &env_var_name.item);
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
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())),
|
||||
}]
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ impl Command for If {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -18,6 +18,10 @@ impl Command for Ignore {
|
||||
Signature::build("ignore").category(Category::Core)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["silent", "quiet", "out-null"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
@ -28,7 +28,7 @@ impl Command for Let {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -1,4 +1,6 @@
|
||||
mod alias;
|
||||
mod ast;
|
||||
mod commandline;
|
||||
mod debug;
|
||||
mod def;
|
||||
mod def_env;
|
||||
@ -10,23 +12,25 @@ mod export;
|
||||
mod export_alias;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_env;
|
||||
mod export_extern;
|
||||
mod export_use;
|
||||
mod extern_;
|
||||
mod for_;
|
||||
pub mod help;
|
||||
mod hide;
|
||||
mod hide_env;
|
||||
mod if_;
|
||||
mod ignore;
|
||||
mod let_;
|
||||
mod metadata;
|
||||
mod module;
|
||||
pub(crate) mod overlay;
|
||||
mod source;
|
||||
mod use_;
|
||||
mod version;
|
||||
|
||||
pub use alias::Alias;
|
||||
pub use ast::Ast;
|
||||
pub use commandline::Commandline;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
pub use def_env::DefEnv;
|
||||
@ -38,19 +42,19 @@ pub use export::ExportCommand;
|
||||
pub use export_alias::ExportAlias;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_env::ExportEnv;
|
||||
pub use export_extern::ExportExtern;
|
||||
pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
pub use for_::For;
|
||||
pub use help::Help;
|
||||
pub use hide::Hide;
|
||||
pub use hide_env::HideEnv;
|
||||
pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use let_::Let;
|
||||
pub use metadata::Metadata;
|
||||
pub use module::Module;
|
||||
pub use overlay::*;
|
||||
pub use source::Source;
|
||||
pub use use_::Use;
|
||||
pub use version::Version;
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -27,7 +27,7 @@ impl Command for Module {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -55,8 +55,8 @@ impl Command for Module {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module and evaluate it",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
description: "Define an environment variable in a module",
|
||||
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
|
@ -1,148 +0,0 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayAdd;
|
||||
|
||||
impl Command for OverlayAdd {
|
||||
fn name(&self) -> &str {
|
||||
"overlay add"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Add definitions from a module as an overlay"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("overlay add")
|
||||
.required(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"Module name to create overlay for",
|
||||
)
|
||||
// TODO:
|
||||
// .switch(
|
||||
// "prefix",
|
||||
// "Prepend module name to the imported symbols",
|
||||
// Some('p'),
|
||||
// )
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name_arg: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let maybe_overlay_name = if engine_state
|
||||
.find_overlay(name_arg.item.as_bytes())
|
||||
.is_some()
|
||||
{
|
||||
Some(name_arg.item.clone())
|
||||
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
|
||||
os_str.to_str().map(|name| name.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(overlay_name) = maybe_overlay_name {
|
||||
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.as_bytes()) {
|
||||
let old_module_id = engine_state.get_overlay(overlay_id).origin;
|
||||
|
||||
stack.add_overlay(overlay_name.clone());
|
||||
|
||||
if let Some(new_module_id) = engine_state.find_module(overlay_name.as_bytes(), &[])
|
||||
{
|
||||
if !stack.has_env_overlay(&overlay_name, engine_state)
|
||||
|| (old_module_id != new_module_id)
|
||||
{
|
||||
// Add environment variables only if:
|
||||
// a) adding a new overlay
|
||||
// b) refreshing an active overlay (the origin module changed)
|
||||
let module = engine_state.get_module(new_module_id);
|
||||
|
||||
for (name, block_id) in module.env_vars() {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(call.head));
|
||||
};
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let val = eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
PipelineData::new(call.head),
|
||||
false,
|
||||
true,
|
||||
)?
|
||||
.into_value(call.head);
|
||||
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
name_arg.item,
|
||||
name_arg.span,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
name_arg.item,
|
||||
name_arg.span,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create an overlay from a module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay add spam"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay from a file",
|
||||
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
|
||||
overlay add spam.nu"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(OverlayAdd {})
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ impl Command for Overlay {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -4,29 +4,29 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayRemove;
|
||||
pub struct OverlayHide;
|
||||
|
||||
impl Command for OverlayRemove {
|
||||
impl Command for OverlayHide {
|
||||
fn name(&self) -> &str {
|
||||
"overlay remove"
|
||||
"overlay hide"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove an active overlay"
|
||||
"Hide an active overlay"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("overlay remove")
|
||||
.optional("name", SyntaxShape::String, "Overlay to remove")
|
||||
Signature::build("overlay hide")
|
||||
.optional("name", SyntaxShape::String, "Overlay to hide")
|
||||
.switch(
|
||||
"keep-custom",
|
||||
"Keep all newly added symbols within the next activated overlay",
|
||||
"Keep all newly added commands and aliases in the next activated overlay",
|
||||
Some('k'),
|
||||
)
|
||||
.named(
|
||||
"keep-env",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||
"List of environment variables to keep from the removed overlay",
|
||||
"List of environment variables to keep in the next activated overlay",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
@ -34,7 +34,7 @@ impl Command for OverlayRemove {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -67,23 +67,7 @@ impl Command for OverlayRemove {
|
||||
let keep_env: Option<Vec<Spanned<String>>> =
|
||||
call.get_flag(engine_state, stack, "keep-env")?;
|
||||
|
||||
let env_vars_to_keep = if call.has_flag("keep-custom") {
|
||||
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
|
||||
let overlay_frame = engine_state.get_overlay(overlay_id);
|
||||
let origin_module = engine_state.get_module(overlay_frame.origin);
|
||||
|
||||
stack
|
||||
.get_overlay_env_vars(engine_state, &overlay_name.item)
|
||||
.into_iter()
|
||||
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
|
||||
.collect()
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
overlay_name.item,
|
||||
overlay_name.span,
|
||||
));
|
||||
}
|
||||
} else if let Some(env_var_names_to_keep) = keep_env {
|
||||
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
|
||||
let mut env_vars_to_keep = vec![];
|
||||
|
||||
for name in env_var_names_to_keep.into_iter() {
|
||||
@ -110,31 +94,34 @@ impl Command for OverlayRemove {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove an overlay created from a module",
|
||||
description: "Keep a custom command after hiding the overlay",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay add spam
|
||||
overlay remove spam"#,
|
||||
overlay use spam
|
||||
def bar [] { "bar" }
|
||||
overlay hide spam --keep-custom
|
||||
bar
|
||||
"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Remove an overlay created from a file",
|
||||
description: "Hide an overlay created from a file",
|
||||
example: r#"echo 'export alias f = "foo"' | save spam.nu
|
||||
overlay add spam.nu
|
||||
overlay remove spam"#,
|
||||
overlay use spam.nu
|
||||
overlay hide spam"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Remove the last activated overlay",
|
||||
example: r#"module spam { export env FOO { "foo" } }
|
||||
overlay add spam
|
||||
overlay remove"#,
|
||||
description: "Hide the last activated overlay",
|
||||
example: r#"module spam { export-env { let-env FOO = "foo" } }
|
||||
overlay use spam
|
||||
overlay hide"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Keep the current working directory when removing an overlay",
|
||||
example: r#"overlay new spam
|
||||
cd some-dir
|
||||
overlay remove --keep-env [ PWD ] spam"#,
|
||||
overlay hide --keep-env [ PWD ] spam"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
@ -4,8 +4,6 @@ use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
|
||||
};
|
||||
|
||||
use log::trace;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayList;
|
||||
|
||||
@ -28,41 +26,17 @@ impl Command for OverlayList {
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let active_overlays_parser: Vec<Value> = engine_state
|
||||
.active_overlay_names(&[])
|
||||
.iter()
|
||||
.map(|s| Value::string(String::from_utf8_lossy(s), call.head))
|
||||
.collect();
|
||||
|
||||
let active_overlays_engine: Vec<Value> = stack
|
||||
.active_overlays
|
||||
.iter()
|
||||
.map(|s| Value::string(s, call.head))
|
||||
.collect();
|
||||
|
||||
// Check if the overlays in the engine match the overlays in the parser
|
||||
if (active_overlays_parser.len() != active_overlays_engine.len())
|
||||
|| active_overlays_parser
|
||||
.iter()
|
||||
.zip(active_overlays_engine.iter())
|
||||
.any(|(op, oe)| op != oe)
|
||||
{
|
||||
trace!("parser overlays: {:?}", active_overlays_parser);
|
||||
trace!("engine overlays: {:?}", active_overlays_engine);
|
||||
|
||||
return Err(ShellError::NushellFailedSpannedHelp(
|
||||
"Overlay mismatch".into(),
|
||||
"Active overlays do not match between the engine and the parser.".into(),
|
||||
call.head,
|
||||
"Run Nushell with --log-level=trace to see what went wrong.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Value::List {
|
||||
vals: active_overlays_engine,
|
||||
span: call.head,
|
||||
@ -74,7 +48,7 @@ impl Command for OverlayList {
|
||||
vec![Example {
|
||||
description: "Get the last activated overlay",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay add spam
|
||||
overlay use spam
|
||||
overlay list | last"#,
|
||||
result: Some(Value::String {
|
||||
val: "spam".to_string(),
|
||||
|
@ -1,11 +1,11 @@
|
||||
mod add;
|
||||
mod command;
|
||||
mod hide;
|
||||
mod list;
|
||||
mod new;
|
||||
mod remove;
|
||||
mod use_;
|
||||
|
||||
pub use add::OverlayAdd;
|
||||
pub use command::Overlay;
|
||||
pub use hide::OverlayHide;
|
||||
pub use list::OverlayList;
|
||||
pub use new::OverlayNew;
|
||||
pub use remove::OverlayRemove;
|
||||
pub use use_::OverlayUse;
|
||||
|
@ -31,7 +31,7 @@ impl Command for OverlayNew {
|
||||
r#"The command will first create an empty module, then add it as an overlay.
|
||||
|
||||
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 {
|
||||
|
212
crates/nu-command/src/core_commands/overlay/use_.rs
Normal file
212
crates/nu-command/src/core_commands/overlay/use_.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::ast::{Call, Expr};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayUse;
|
||||
|
||||
impl Command for OverlayUse {
|
||||
fn name(&self) -> &str {
|
||||
"overlay use"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use definitions from a module as an overlay"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("overlay use")
|
||||
.required(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"Module name to use overlay for",
|
||||
)
|
||||
.optional(
|
||||
"as",
|
||||
SyntaxShape::Keyword(b"as".to_vec(), Box::new(SyntaxShape::String)),
|
||||
"as keyword followed by a new name",
|
||||
)
|
||||
.switch(
|
||||
"prefix",
|
||||
"Prepend module name to the imported commands and aliases",
|
||||
Some('p'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
caller_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||
|
||||
let origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
|
||||
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
||||
module_id
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned(
|
||||
"Not an overlay".to_string(),
|
||||
"requires an overlay (path or a string)".to_string(),
|
||||
overlay_expr.span,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned(
|
||||
"Missing positional".to_string(),
|
||||
"missing required overlay".to_string(),
|
||||
call.head,
|
||||
));
|
||||
};
|
||||
|
||||
let overlay_name = if let Some(kw_expression) = call.positional_nth(1) {
|
||||
// If renamed via the 'as' keyword, use the new name as the overlay name
|
||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
||||
if let Some(new_name) = new_name_expression.as_string() {
|
||||
new_name
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned(
|
||||
"Wrong keyword type".to_string(),
|
||||
"keyword argument not a string".to_string(),
|
||||
new_name_expression.span,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned(
|
||||
"Wrong keyword type".to_string(),
|
||||
"keyword argument not a keyword".to_string(),
|
||||
kw_expression.span,
|
||||
));
|
||||
}
|
||||
} else if engine_state
|
||||
.find_overlay(name_arg.item.as_bytes())
|
||||
.is_some()
|
||||
{
|
||||
name_arg.item.clone()
|
||||
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
|
||||
if let Some(name) = os_str.to_str() {
|
||||
name.to_string()
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(name_arg.span));
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
name_arg.item,
|
||||
name_arg.span,
|
||||
));
|
||||
};
|
||||
|
||||
caller_stack.add_overlay(overlay_name);
|
||||
|
||||
if let Some(module_id) = origin_module_id {
|
||||
// Add environment variables only if:
|
||||
// a) adding a new overlay
|
||||
// b) refreshing an active overlay (the origin module changed)
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
// Evaluate the export-env block (if any) and keep its environment
|
||||
if let Some(block_id) = module.env_block {
|
||||
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
|
||||
|
||||
if let Some(path) = &maybe_path {
|
||||
// Set the currently evaluated directory, if the argument is a valid path
|
||||
let mut parent = path.clone();
|
||||
parent.pop();
|
||||
|
||||
let file_pwd = Value::String {
|
||||
val: parent.to_string_lossy().to_string(),
|
||||
span: call.head,
|
||||
};
|
||||
|
||||
caller_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
|
||||
}
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||
|
||||
let _ = eval_block(
|
||||
engine_state,
|
||||
&mut callee_stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
);
|
||||
|
||||
// Merge the block's environment to the current stack
|
||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||
|
||||
if maybe_path.is_some() {
|
||||
// Remove the file-relative PWD, if the argument is a valid path
|
||||
caller_stack.remove_env_var(engine_state, "FILE_PWD");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create an overlay from a module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam
|
||||
foo"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay from a module and rename it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam as spam_new
|
||||
foo"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay with a prefix",
|
||||
example: r#"echo 'export def foo { "foo" }'
|
||||
overlay use --prefix spam
|
||||
spam foo"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create an overlay from a file",
|
||||
example: r#"echo 'export-env { let-env FOO = "foo" }' | save spam.nu
|
||||
overlay use spam.nu
|
||||
$env.FOO"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(OverlayUse {})
|
||||
}
|
||||
}
|
@ -21,12 +21,6 @@ impl Command for Register {
|
||||
SyntaxShape::Filepath,
|
||||
"path of executable for plugin",
|
||||
)
|
||||
.required_named(
|
||||
"encoding",
|
||||
SyntaxShape::String,
|
||||
"Encoding used to communicate with plugin. Options: [capnp, json]",
|
||||
Some('e'),
|
||||
)
|
||||
.optional(
|
||||
"signature",
|
||||
SyntaxShape::Any,
|
||||
@ -43,7 +37,7 @@ impl Command for Register {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -64,12 +58,12 @@ impl Command for Register {
|
||||
vec![
|
||||
Example {
|
||||
description: "Register `nu_plugin_query` plugin from ~/.cargo/bin/ dir",
|
||||
example: r#"register -e json ~/.cargo/bin/nu_plugin_query"#,
|
||||
example: r#"register ~/.cargo/bin/nu_plugin_query"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Register `nu_plugin_query` plugin from `nu -c`(plugin will be available in that nu session only)",
|
||||
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register -e json ($plugin); version'"#,
|
||||
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::eval_block;
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
||||
use nu_engine::{eval_block, find_in_dirs_env, redirect_env};
|
||||
use nu_protocol::ast::{Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
@ -25,7 +25,7 @@ impl Command for Use {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -35,9 +35,9 @@ impl Command for Use {
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
caller_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let import_pattern = if let Some(Expression {
|
||||
expr: Expr::ImportPattern(pat),
|
||||
@ -58,68 +58,47 @@ impl Command for Use {
|
||||
if let Some(module_id) = import_pattern.head.id {
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let env_vars_to_use = 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(id) = module.get_env_var_id(name) {
|
||||
output.push((name.clone(), id));
|
||||
} else if !module.has_decl(name) && !module.has_alias(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(id) = module.get_env_var_id(name) {
|
||||
output.push((name.clone(), id));
|
||||
} else if !module.has_decl(name) && !module.has_alias(name) {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
||||
String::from_utf8_lossy(name).into(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (name, block_id) in env_vars_to_use {
|
||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
||||
};
|
||||
|
||||
// Evaluate the export-env block if there is one
|
||||
if let Some(block_id) = module.env_block {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let val = eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
PipelineData::new(call.head),
|
||||
false,
|
||||
true,
|
||||
)?
|
||||
.into_value(call.head);
|
||||
// See if the module is a file
|
||||
let module_arg_str = String::from_utf8_lossy(
|
||||
engine_state.get_span_contents(&import_pattern.head.span),
|
||||
);
|
||||
let maybe_parent = if let Some(path) =
|
||||
find_in_dirs_env(&module_arg_str, engine_state, caller_stack)?
|
||||
{
|
||||
path.parent().map(|p| p.to_path_buf()).or(None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
stack.add_env_var(name, val);
|
||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||
|
||||
// If so, set the currently evaluated directory (file-relative PWD)
|
||||
if let Some(parent) = maybe_parent {
|
||||
let file_pwd = Value::String {
|
||||
val: parent.to_string_lossy().to_string(),
|
||||
span: call.head,
|
||||
};
|
||||
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
|
||||
}
|
||||
|
||||
// Run the block (discard the result)
|
||||
let _ = eval_block(
|
||||
engine_state,
|
||||
&mut callee_stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?;
|
||||
|
||||
// Merge the block's environment to the current stack
|
||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||
}
|
||||
} else {
|
||||
// TODO: This is a workaround since call.positional[0].span points at 0 for some reason
|
||||
// when this error is triggered
|
||||
return Err(ShellError::GenericError(
|
||||
format!(
|
||||
"Could not import from '{}'",
|
||||
@ -145,14 +124,6 @@ impl Command for Use {
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module and evaluate it",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
|
@ -59,7 +59,7 @@ pub fn version(
|
||||
|
||||
cols.push("branch".to_string());
|
||||
vals.push(Value::String {
|
||||
val: shadow_rs::branch(),
|
||||
val: shadow::BRANCH.to_string(),
|
||||
span: call.head,
|
||||
});
|
||||
|
||||
|
@ -38,10 +38,9 @@ impl Command for AndDb {
|
||||
vec![
|
||||
Example {
|
||||
description: "Selects a column from a database with an AND clause",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
@ -49,7 +48,7 @@ impl Command for AndDb {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
@ -62,10 +61,9 @@ impl Command for AndDb {
|
||||
},
|
||||
Example {
|
||||
description: "Creates a AND clause combined with an expression AND",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1 | and ((field a) < 10))
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
@ -73,7 +71,7 @@ impl Command for AndDb {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
@ -127,7 +125,7 @@ impl Command for AndDb {
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
|
@ -32,17 +32,16 @@ impl Command for AliasDb {
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates an alias for a selected table",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| from table_1
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
@ -55,22 +54,20 @@ impl Command for AliasDb {
|
||||
},
|
||||
Example {
|
||||
description: "Creates an alias for a derived table",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from (
|
||||
open db.mysql
|
||||
| into db
|
||||
example: r#"open db.sqlite
|
||||
| from table (
|
||||
open db.sqlite
|
||||
| from table table_a
|
||||
| select a b
|
||||
| from table_a
|
||||
)
|
||||
| select a
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
@ -116,7 +113,7 @@ fn alias_db(
|
||||
Vec::new(),
|
||||
)),
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => match &mut query.body {
|
||||
Statement::Query(query) => match &mut *query.body {
|
||||
SetExpr::Select(select) => {
|
||||
select.as_mut().from.iter_mut().for_each(|table| {
|
||||
let new_alias = Some(TableAlias {
|
||||
@ -148,15 +145,13 @@ fn alias_db(
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
@ -28,13 +28,13 @@ impl Command for CollectDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Collect from a select query",
|
||||
example: "open foo.db | into db | select a | from table_1 | collect",
|
||||
example: "open foo.db | from table table_1 db | select a | collect",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "collect"]
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -17,7 +17,7 @@ pub fn value_into_table_factor(
|
||||
Ok(TableFactor::Table {
|
||||
name: ObjectName(vec![ident]),
|
||||
alias,
|
||||
args: Vec::new(),
|
||||
args: None,
|
||||
with_hints: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl Command for DescribeDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Describe SQLite database constructed query",
|
||||
example: "open foo.db | into db | select col_1 | from table_1 | describe",
|
||||
example: "open foo.db | from table table_1 | select col_1 | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
@ -47,7 +47,7 @@ impl Command for DescribeDb {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "SQLite", "describe"]
|
||||
vec!["database", "SQLite"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -15,7 +15,7 @@ pub struct FromDb;
|
||||
|
||||
impl Command for FromDb {
|
||||
fn name(&self) -> &str {
|
||||
"from"
|
||||
"from table"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -35,24 +35,24 @@ impl Command for FromDb {
|
||||
"Alias for the selected table",
|
||||
Some('a'),
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "from"]
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Selects a table from database",
|
||||
example: "open db.mysql | into db | from table_a | describe",
|
||||
example: "open db.sqlite | from table table_a | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
@ -96,12 +96,12 @@ fn create_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
let query = Query {
|
||||
with: None,
|
||||
body: SetExpr::Select(Box::new(create_select(
|
||||
body: Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?)),
|
||||
)?))),
|
||||
order_by: Vec::new(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
@ -121,18 +121,18 @@ fn modify_statement(
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match query.body {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => {
|
||||
let table = create_table(connection, engine_state, stack, call)?;
|
||||
select.from.push(table);
|
||||
}
|
||||
_ => {
|
||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(
|
||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?));
|
||||
)?)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -167,6 +167,7 @@ fn create_select(
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
})
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user