mirror of
https://github.com/nushell/nushell.git
synced 2025-07-02 23:51:49 +02:00
Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
1aa2ed1947 | |||
f04db2a7a3 | |||
30b3c42b37 | |||
4424481487 | |||
8b431e3a2e | |||
c58b432c21 | |||
34c09d8b35 | |||
13d5a15f75 | |||
339c5b7c83 | |||
5291f978c2 | |||
63fa6a6df7 | |||
4540f3829e | |||
1349187e17 | |||
b55ed69c92 | |||
948965d42f | |||
f4205132c7 | |||
5eae08ac76 | |||
3836da0cf1 | |||
6be42d94d9 | |||
945e9511ce | |||
cce12efe48 | |||
aa62de78e6 | |||
08b5d5cce5 | |||
03bb144150 | |||
5fa79e6e5f | |||
080b501ba8 | |||
66bc0542e0 | |||
ec1f7deb23 | |||
45f9d03025 | |||
4bc28f1752 | |||
a2705f9eb5 | |||
c0b4d19761 | |||
b53271b86a | |||
5ca4e903c8 | |||
0ad5f4389c | |||
7ea4895513 | |||
f46f8b286b | |||
f88ed6ecd5 | |||
a011791631 | |||
c783b07d58 | |||
e3e2554b3d | |||
926b0407c5 | |||
22a01d7e76 | |||
299453ecb7 | |||
fd684a204c | |||
cdbb3ee7b9 | |||
f0f6b3a3e5 | |||
93e121782c | |||
befeddad59 | |||
73c08fcb2b | |||
9a0ae7c4c0 | |||
28ca0e7116 | |||
84c720daf5 | |||
cdb082e92d | |||
0666b3784f | |||
379d89369c | |||
2bd345c367 | |||
0e418688d4 | |||
b97d89adb6 | |||
ee84435a0e | |||
500cd35ad2 | |||
3f5ebd75b6 | |||
75105033b2 | |||
8759936636 | |||
4dcaf2a201 | |||
089c5221cc | |||
0587308684 | |||
6eff420e17 | |||
d66f8cca40 | |||
06938659d2 | |||
46566296c0 | |||
4e1b06cb51 | |||
b99a8c9d80 | |||
b34547334a | |||
d9bfcb4c09 | |||
8ce14a7c86 | |||
301d1370c4 | |||
306e305b65 | |||
e117706518 | |||
737ea3940e | |||
e5337b50a9 | |||
23dc1b600a | |||
f05162811c | |||
0b71eb201c | |||
707ab1df6a | |||
c811d86dbd | |||
902e6d7a27 | |||
827e31191d | |||
b9b3101bd9 | |||
8e8a60a432 | |||
72d50cf8b7 | |||
3a1601de8e | |||
3f8dd1b705 | |||
f360489f1e | |||
79f19f2fc7 | |||
5cf6dea997 | |||
214714e0ab | |||
d894c8befe | |||
cc4d4acc6b | |||
dc52a6fec5 | |||
16e174be7e | |||
8e41a308cd | |||
787f292ca7 | |||
dad956b2ee | |||
1f477c8eb1 | |||
6260fa9f07 | |||
88f44701a9 | |||
9ed944312f | |||
6eb14522b6 | |||
ac12b02437 | |||
9ed2ca792f | |||
ebabca575c | |||
b60f91f722 | |||
2b4c54d383 | |||
ed1381adc4 | |||
1b7fabd1fd | |||
87a562e24b | |||
b5ff46db6a | |||
8b086d3613 | |||
d702c4605a | |||
6325bc5e54 | |||
25d90fa603 | |||
86f7f53f85 | |||
461eb43d9d | |||
df3892f323 | |||
0d3f76ddef | |||
816b9a6953 | |||
80788636ee | |||
c46ca36bcd | |||
62bd6fe08b | |||
f69b22f00b | |||
c6523eb8d9 | |||
76afa74320 | |||
a0d4ae18ee | |||
4884894ddb | |||
e7877db078 | |||
1181349c22 | |||
378395c22c | |||
2bcf2389aa | |||
a65e5ab01d | |||
4ff4e3f93d | |||
d36514a323 | |||
4401924128 | |||
5314b31b12 | |||
b2b5b89a92 | |||
76bbd41e43 | |||
5f3c8d45d8 | |||
38694a9850 | |||
0a0475ebad | |||
38ffcaad7b | |||
1b01598840 | |||
45ff964cbd | |||
81baf53814 | |||
6ebc0fc3ff | |||
b1da50774a | |||
469e23cae4 | |||
23ba613b00 | |||
f2dcae570c | |||
f1ce0c98fd | |||
35d2750757 | |||
4b1f4e63c3 | |||
c29bcc94e7 | |||
d3cbcf401f |
58
.github/workflows/ci.yml
vendored
58
.github/workflows/ci.yml
vendored
@ -96,7 +96,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --no-default-features --force
|
||||||
|
|
||||||
- name: Standard library tests
|
- name: Standard library tests
|
||||||
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
||||||
@ -163,7 +163,23 @@ jobs:
|
|||||||
echo "no changes in working directory";
|
echo "no changes in working directory";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build-wasm:
|
wasm:
|
||||||
|
env:
|
||||||
|
WASM_OPTIONS: --no-default-features --target wasm32-unknown-unknown
|
||||||
|
CLIPPY_CONF_DIR: ${{ github.workspace }}/clippy/wasm/
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- name: Build WASM
|
||||||
|
command: cargo build
|
||||||
|
args:
|
||||||
|
- name: Clippy WASM
|
||||||
|
command: cargo clippy
|
||||||
|
args: -- $CLIPPY_OPTIONS
|
||||||
|
|
||||||
|
name: ${{ matrix.job.name }}
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
@ -174,22 +190,22 @@ jobs:
|
|||||||
- name: Add wasm32-unknown-unknown target
|
- name: Add wasm32-unknown-unknown target
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
- run: cargo build -p nu-cmd-base --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-base $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-cmd-extra --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-extra $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-cmd-lang --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-lang $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-color-config --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-color-config $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-command --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-command $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-derive-value --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-derive-value $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-engine --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-engine $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-glob --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-glob $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-json --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-json $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-parser --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-parser $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-path --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-path $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-pretty-hex --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-pretty-hex $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-protocol --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-protocol $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-std --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-std $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-system --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-system $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-table --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-table $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-term-grid --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-term-grid $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-utils --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-utils $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nuon --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nuon $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
|
2
.github/workflows/milestone.yml
vendored
2
.github/workflows/milestone.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
# Bind milestone to closed issue that has a merged PR fix
|
# Bind milestone to closed issue that has a merged PR fix
|
||||||
- name: Set Milestone for Issue
|
- name: Set Milestone for Issue
|
||||||
uses: hustcer/milestone-action@main
|
uses: hustcer/milestone-action@v2
|
||||||
if: github.event.issue.state == 'closed'
|
if: github.event.issue.state == 'closed'
|
||||||
with:
|
with:
|
||||||
action: bind-issue
|
action: bind-issue
|
||||||
|
8
.github/workflows/nightly-build.yml
vendored
8
.github/workflows/nightly-build.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.101.0
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
@ -114,7 +114,7 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-musleabihf
|
- target: armv7-unknown-linux-musleabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.101.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -197,7 +197,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.101.0
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -7,7 +7,9 @@ name: Create Release Draft
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||||
|
- '!*nightly*' # Don't trigger release for nightly tags
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@ -64,7 +66,7 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-musleabihf
|
- target: armv7-unknown-linux-musleabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.101.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.28.4
|
uses: crate-ci/typos@v1.29.4
|
||||||
|
1608
Cargo.lock
generated
1608
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
87
Cargo.toml
87
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.81.0"
|
rust-version = "1.82.0"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ ansi-str = "0.8"
|
|||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bracoxide = "0.1.4"
|
bracoxide = "0.1.4"
|
||||||
brotli = "6.0"
|
brotli = "7.0"
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytesize = "1.3"
|
bytesize = "1.3"
|
||||||
@ -80,6 +80,7 @@ crossbeam-channel = "0.5.8"
|
|||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
devicons = "0.6.12"
|
||||||
dialoguer = { default-features = false, version = "0.11" }
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
@ -89,7 +90,6 @@ encoding_rs = "0.8"
|
|||||||
fancy-regex = "0.14"
|
fancy-regex = "0.14"
|
||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fuzzy-matcher = "0.3"
|
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
human-date-parser = "0.2.0"
|
human-date-parser = "0.2.0"
|
||||||
indexmap = "2.7"
|
indexmap = "2.7"
|
||||||
@ -102,8 +102,9 @@ libproc = "0.14"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru = "0.12"
|
lru = "0.12"
|
||||||
lscolors = { version = "0.17", default-features = false }
|
lscolors = { version = "0.17", default-features = false }
|
||||||
lsp-server = "0.7.5"
|
lsp-server = "0.7.8"
|
||||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
lsp-types = { version = "0.97.0", features = ["proposed"] }
|
||||||
|
lsp-textdocument = "0.4.1"
|
||||||
mach2 = "0.4"
|
mach2 = "0.4"
|
||||||
md5 = { version = "0.10", package = "md-5" }
|
md5 = { version = "0.10", package = "md-5" }
|
||||||
miette = "7.3"
|
miette = "7.3"
|
||||||
@ -115,6 +116,7 @@ native-tls = "0.2"
|
|||||||
nix = { version = "0.29", default-features = false }
|
nix = { version = "0.29", default-features = false }
|
||||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
nu-ansi-term = "0.50.1"
|
nu-ansi-term = "0.50.1"
|
||||||
|
nucleo-matcher = "0.3"
|
||||||
num-format = "0.4"
|
num-format = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
oem_cp = "2.0.0"
|
oem_cp = "2.0.0"
|
||||||
@ -139,49 +141,50 @@ rand_chacha = "0.3.1"
|
|||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
reedline = "0.38.0"
|
reedline = "0.38.0"
|
||||||
regex = "1.9.5"
|
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
ropey = "1.6.1"
|
|
||||||
roxmltree = "0.20"
|
roxmltree = "0.20"
|
||||||
rstest = { version = "0.23", default-features = false }
|
rstest = { version = "0.23", default-features = false }
|
||||||
|
rstest_reuse = "0.7"
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
scopeguard = { version = "1.2.0" }
|
scopeguard = { version = "1.2.0" }
|
||||||
serde = { version = "1.0" }
|
serde = { version = "1.0" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9.33"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
sysinfo = "0.32"
|
sysinfo = "0.33"
|
||||||
tabled = { version = "0.16.0", default-features = false }
|
tabled = { version = "0.17.0", default-features = false }
|
||||||
tempfile = "3.14"
|
tempfile = "3.15"
|
||||||
terminal_size = "0.4"
|
|
||||||
titlecase = "3.0"
|
titlecase = "3.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
|
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
|
||||||
umask = "2.1"
|
umask = "2.1"
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.12"
|
||||||
unicode-width = "0.2"
|
unicode-width = "0.2"
|
||||||
ureq = { version = "2.12", default-features = false }
|
ureq = { version = "2.12", default-features = false }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.28"
|
uu_cp = "0.0.29"
|
||||||
uu_mkdir = "0.0.28"
|
uu_mkdir = "0.0.29"
|
||||||
uu_mktemp = "0.0.28"
|
uu_mktemp = "0.0.29"
|
||||||
uu_mv = "0.0.28"
|
uu_mv = "0.0.29"
|
||||||
uu_touch = "0.0.28"
|
uu_touch = "0.0.29"
|
||||||
uu_whoami = "0.0.28"
|
uu_whoami = "0.0.29"
|
||||||
uu_uname = "0.0.28"
|
uu_uname = "0.0.29"
|
||||||
uucore = "0.0.28"
|
uucore = "0.0.29"
|
||||||
uuid = "1.11.0"
|
uuid = "1.12.0"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
|
web-time = "1.1.0"
|
||||||
which = "7.0.0"
|
which = "7.0.0"
|
||||||
windows = "0.56"
|
windows = "0.56"
|
||||||
windows-sys = "0.48"
|
windows-sys = "0.48"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
memchr = "2.7.4"
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||||
@ -192,22 +195,22 @@ unchecked_duration_subtraction = "warn"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.101.0" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.102.0" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.102.0" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.102.0" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.101.0", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.102.0", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.101.0" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.102.0" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.101.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.102.0" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.101.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.102.0" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.101.0" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.102.0" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.101.0" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.102.0" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.101.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.102.0" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.101.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.102.0" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.101.0" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.102.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.101.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.102.0" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.101.0" }
|
nu-std = { path = "./crates/nu-std", version = "0.102.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.101.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.102.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.101.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.102.0" }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
@ -237,14 +240,14 @@ nix = { workspace = true, default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.102.0" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.101.0" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.102.0" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.101.0" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.102.0" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
tango-bench = "0.6"
|
tango-bench = "0.6"
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
serial_test = "3.2"
|
serial_test = "3.2"
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
112
README.md
112
README.md
@ -95,44 +95,44 @@ Commands that work in the pipeline fit into one of three categories:
|
|||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "dir" | table
|
ls | where type == "dir" | table
|
||||||
╭────┬──────────┬──────┬─────────┬───────────────╮
|
# => ╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||||
│ # │ name │ type │ size │ modified │
|
# => │ # │ name │ type │ size │ modified │
|
||||||
├────┼──────────┼──────┼─────────┼───────────────┤
|
# => ├────┼──────────┼──────┼─────────┼───────────────┤
|
||||||
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
# => │ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||||
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
# => │ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
# => │ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
# => │ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
# => │ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
# => │ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
# => │ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 9 │ target │ dir │ 0 B │ a day ago │
|
# => │ 9 │ target │ dir │ 0 B │ a day ago │
|
||||||
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
# => │ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||||
╰────┴──────────┴──────┴─────────┴───────────────╯
|
# => ╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
||||||
We could have also written the above:
|
We could have also written the above:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "dir"
|
ls | where type == "dir"
|
||||||
```
|
```
|
||||||
|
|
||||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||||
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ps | where cpu > 0
|
ps | where cpu > 0
|
||||||
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
# => ╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||||
│ # │ pid │ name │ cpu │ mem │ virtual │
|
# => │ # │ pid │ name │ cpu │ mem │ virtual │
|
||||||
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
# => ├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||||
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
# => │ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||||
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
# => │ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||||
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
# => │ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||||
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
# => ╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opening files
|
### Opening files
|
||||||
@ -141,46 +141,46 @@ Nu can load file and URL contents as raw text or structured data (if it recogniz
|
|||||||
For example, you can load a .toml file as structured data and explore it:
|
For example, you can load a .toml file as structured data and explore it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml
|
open Cargo.toml
|
||||||
╭──────────────────┬────────────────────╮
|
# => ╭──────────────────┬────────────────────╮
|
||||||
│ bin │ [table 1 row] │
|
# => │ bin │ [table 1 row] │
|
||||||
│ dependencies │ {record 25 fields} │
|
# => │ dependencies │ {record 25 fields} │
|
||||||
│ dev-dependencies │ {record 8 fields} │
|
# => │ dev-dependencies │ {record 8 fields} │
|
||||||
│ features │ {record 10 fields} │
|
# => │ features │ {record 10 fields} │
|
||||||
│ package │ {record 13 fields} │
|
# => │ package │ {record 13 fields} │
|
||||||
│ patch │ {record 1 field} │
|
# => │ patch │ {record 1 field} │
|
||||||
│ profile │ {record 3 fields} │
|
# => │ profile │ {record 3 fields} │
|
||||||
│ target │ {record 3 fields} │
|
# => │ target │ {record 3 fields} │
|
||||||
│ workspace │ {record 1 field} │
|
# => │ workspace │ {record 1 field} │
|
||||||
╰──────────────────┴────────────────────╯
|
# => ╰──────────────────┴────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
We can pipe this into a command that gets the contents of one of the columns:
|
We can pipe this into a command that gets the contents of one of the columns:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package
|
open Cargo.toml | get package
|
||||||
╭───────────────┬────────────────────────────────────╮
|
# => ╭───────────────┬────────────────────────────────────╮
|
||||||
│ authors │ [list 1 item] │
|
# => │ authors │ [list 1 item] │
|
||||||
│ default-run │ nu │
|
# => │ default-run │ nu │
|
||||||
│ description │ A new type of shell │
|
# => │ description │ A new type of shell │
|
||||||
│ documentation │ https://www.nushell.sh/book/ │
|
# => │ documentation │ https://www.nushell.sh/book/ │
|
||||||
│ edition │ 2018 │
|
# => │ edition │ 2018 │
|
||||||
│ exclude │ [list 1 item] │
|
# => │ exclude │ [list 1 item] │
|
||||||
│ homepage │ https://www.nushell.sh │
|
# => │ homepage │ https://www.nushell.sh │
|
||||||
│ license │ MIT │
|
# => │ license │ MIT │
|
||||||
│ metadata │ {record 1 field} │
|
# => │ metadata │ {record 1 field} │
|
||||||
│ name │ nu │
|
# => │ name │ nu │
|
||||||
│ repository │ https://github.com/nushell/nushell │
|
# => │ repository │ https://github.com/nushell/nushell │
|
||||||
│ rust-version │ 1.60 │
|
# => │ rust-version │ 1.60 │
|
||||||
│ version │ 0.72.0 │
|
# => │ version │ 0.72.0 │
|
||||||
╰───────────────┴────────────────────────────────────╯
|
# => ╰───────────────┴────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
And if needed we can drill down further:
|
And if needed we can drill down further:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version
|
open Cargo.toml | get package.version
|
||||||
0.72.0
|
# => 0.72.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
3
clippy/wasm/clippy.toml
Normal file
3
clippy/wasm/clippy.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[[disallowed-types]]
|
||||||
|
path = "std::time::Instant"
|
||||||
|
reason = "WASM panics if used, use `web_time::Instant` instead"
|
@ -5,38 +5,39 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.102.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.101.0" }
|
nu-command = { path = "../nu-command", version = "0.102.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.102.0" }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.102.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", features = ["os"] }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", features = ["os"] }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-glob = { path = "../nu-glob", version = "0.102.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.102.0" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.101.0", optional = true }
|
nu-parser = { path = "../nu-parser", version = "0.102.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", features = ["os"] }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.102.0", optional = true }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", features = ["os"] }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.101.0" }
|
nu-utils = { path = "../nu-utils", version = "0.102.0" }
|
||||||
|
nu-color-config = { path = "../nu-color-config", version = "0.102.0" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], workspace = true }
|
chrono = { default-features = false, features = ["std"], workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
fuzzy-matcher = { workspace = true }
|
|
||||||
is_executable = { workspace = true }
|
is_executable = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
|
||||||
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
||||||
|
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||||
|
nucleo-matcher = { workspace = true }
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::HistoryFileFormat;
|
use nu_protocol::{shell_error::io::IoError, HistoryFileFormat};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
||||||
SqliteBackedHistory,
|
SqliteBackedHistory,
|
||||||
@ -93,10 +93,11 @@ impl Command for History {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound {
|
.ok_or(IoError::new(
|
||||||
file: history_path.display().to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: head,
|
head,
|
||||||
})?
|
history_path,
|
||||||
|
))?
|
||||||
.into_pipeline_data(head, signals)),
|
.into_pipeline_data(head, signals)),
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
@ -109,10 +110,11 @@ impl Command for History {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound {
|
.ok_or(IoError::new(
|
||||||
file: history_path.display().to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: head,
|
head,
|
||||||
})?
|
history_path,
|
||||||
|
))?
|
||||||
.into_pipeline_data(head, signals)),
|
.into_pipeline_data(head, signals)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::HistoryFileFormat;
|
use nu_protocol::{
|
||||||
|
shell_error::{self, io::IoError},
|
||||||
|
HistoryFileFormat,
|
||||||
|
};
|
||||||
|
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History, HistoryItem, ReedlineError, SearchQuery, SqliteBackedHistory,
|
FileBackedHistory, History, HistoryItem, ReedlineError, SearchQuery, SqliteBackedHistory,
|
||||||
@ -35,6 +38,7 @@ Note that history item IDs are ignored when importing from file."#
|
|||||||
.category(Category::History)
|
.category(Category::History)
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Nothing, Type::Nothing),
|
(Type::Nothing, Type::Nothing),
|
||||||
|
(Type::String, Type::Nothing),
|
||||||
(Type::List(Box::new(Type::String)), Type::Nothing),
|
(Type::List(Box::new(Type::String)), Type::Nothing),
|
||||||
(Type::table(), Type::Nothing),
|
(Type::table(), Type::Nothing),
|
||||||
])
|
])
|
||||||
@ -68,17 +72,16 @@ Note that history item IDs are ignored when importing from file."#
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = call.head;
|
||||||
let ok = Ok(Value::nothing(call.head).into_pipeline_data());
|
let ok = Ok(Value::nothing(call.head).into_pipeline_data());
|
||||||
|
|
||||||
let Some(history) = engine_state.history_config() else {
|
let Some(history) = engine_state.history_config() else {
|
||||||
return ok;
|
return ok;
|
||||||
};
|
};
|
||||||
let Some(current_history_path) = history.file_path() else {
|
let Some(current_history_path) = history.file_path() else {
|
||||||
return Err(ShellError::ConfigDirNotFound {
|
return Err(ShellError::ConfigDirNotFound { span: span.into() });
|
||||||
span: Some(call.head),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
if let Some(bak_path) = backup(¤t_history_path)? {
|
if let Some(bak_path) = backup(¤t_history_path, span)? {
|
||||||
println!("Backed history to {}", bak_path.display());
|
println!("Backed history to {}", bak_path.display());
|
||||||
}
|
}
|
||||||
match input {
|
match input {
|
||||||
@ -215,7 +218,7 @@ fn item_from_record(mut rec: Record, span: Span) -> Result<HistoryItem, ShellErr
|
|||||||
hostname: get(rec, fields::HOSTNAME, |v| Ok(v.as_str()?.to_owned()))?,
|
hostname: get(rec, fields::HOSTNAME, |v| Ok(v.as_str()?.to_owned()))?,
|
||||||
cwd: get(rec, fields::CWD, |v| Ok(v.as_str()?.to_owned()))?,
|
cwd: get(rec, fields::CWD, |v| Ok(v.as_str()?.to_owned()))?,
|
||||||
exit_status: get(rec, fields::EXIT_STATUS, |v| v.as_int())?,
|
exit_status: get(rec, fields::EXIT_STATUS, |v| v.as_int())?,
|
||||||
duration: get(rec, fields::DURATION, duration_from_value)?,
|
duration: get(rec, fields::DURATION, |v| duration_from_value(v, span))?,
|
||||||
more_info: None,
|
more_info: None,
|
||||||
// TODO: Currently reedline doesn't let you create session IDs.
|
// TODO: Currently reedline doesn't let you create session IDs.
|
||||||
session_id: None,
|
session_id: None,
|
||||||
@ -231,19 +234,21 @@ fn item_from_record(mut rec: Record, span: Span) -> Result<HistoryItem, ShellErr
|
|||||||
Ok(item)
|
Ok(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn duration_from_value(v: Value) -> Result<std::time::Duration, ShellError> {
|
fn duration_from_value(v: Value, span: Span) -> Result<std::time::Duration, ShellError> {
|
||||||
chrono::Duration::nanoseconds(v.as_duration()?)
|
chrono::Duration::nanoseconds(v.as_duration()?)
|
||||||
.to_std()
|
.to_std()
|
||||||
.map_err(|_| ShellError::IOError {
|
.map_err(|_| ShellError::NeedsPositiveValue { span })
|
||||||
msg: "negative duration not supported".to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_backup_path(path: &Path) -> Result<PathBuf, ShellError> {
|
fn find_backup_path(path: &Path, span: Span) -> Result<PathBuf, ShellError> {
|
||||||
let Ok(mut bak_path) = path.to_path_buf().into_os_string().into_string() else {
|
let Ok(mut bak_path) = path.to_path_buf().into_os_string().into_string() else {
|
||||||
// This isn't fundamentally problem, but trying to work with OsString is a nightmare.
|
// This isn't fundamentally problem, but trying to work with OsString is a nightmare.
|
||||||
return Err(ShellError::IOError {
|
return Err(ShellError::GenericError {
|
||||||
msg: "History path mush be representable as UTF-8".to_string(),
|
error: "History path not UTF-8".to_string(),
|
||||||
|
msg: "History path must be representable as UTF-8".to_string(),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
bak_path.push_str(".bak");
|
bak_path.push_str(".bak");
|
||||||
@ -259,24 +264,45 @@ fn find_backup_path(path: &Path) -> Result<PathBuf, ShellError> {
|
|||||||
return Ok(PathBuf::from(bak_path));
|
return Ok(PathBuf::from(bak_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ShellError::IOError {
|
Err(ShellError::GenericError {
|
||||||
msg: "Too many existing backup files".to_string(),
|
error: "Too many backup files".to_string(),
|
||||||
|
msg: "Found too many existing backup files".to_string(),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backup(path: &Path) -> Result<Option<PathBuf>, ShellError> {
|
fn backup(path: &Path, span: Span) -> Result<Option<PathBuf>, ShellError> {
|
||||||
match path.metadata() {
|
match path.metadata() {
|
||||||
Ok(md) if md.is_file() => (),
|
Ok(md) if md.is_file() => (),
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
return Err(ShellError::IOError {
|
return Err(IoError::new_with_additional_context(
|
||||||
msg: "history path exists but is not a file".to_string(),
|
shell_error::io::ErrorKind::NotAFile,
|
||||||
})
|
span,
|
||||||
|
PathBuf::from(path),
|
||||||
|
"history path exists but is not a file",
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => {
|
||||||
|
return Err(IoError::new_internal(
|
||||||
|
e.kind(),
|
||||||
|
"Could not get metadata",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
let bak_path = find_backup_path(path)?;
|
}
|
||||||
std::fs::copy(path, &bak_path)?;
|
let bak_path = find_backup_path(path, span)?;
|
||||||
|
std::fs::copy(path, &bak_path).map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not copy backup",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Ok(Some(bak_path))
|
Ok(Some(bak_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +413,7 @@ mod tests {
|
|||||||
for name in existing {
|
for name in existing {
|
||||||
std::fs::File::create_new(dir.path().join(name)).unwrap();
|
std::fs::File::create_new(dir.path().join(name)).unwrap();
|
||||||
}
|
}
|
||||||
let got = find_backup_path(&dir.path().join("history.dat")).unwrap();
|
let got = find_backup_path(&dir.path().join("history.dat"), Span::test_data()).unwrap();
|
||||||
assert_eq!(got, dir.path().join(want))
|
assert_eq!(got, dir.path().join(want))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +425,7 @@ mod tests {
|
|||||||
write!(&mut history, "123").unwrap();
|
write!(&mut history, "123").unwrap();
|
||||||
let want_bak_path = dir.path().join("history.dat.bak");
|
let want_bak_path = dir.path().join("history.dat.bak");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
backup(&dir.path().join("history.dat")),
|
backup(&dir.path().join("history.dat"), Span::test_data()),
|
||||||
Ok(Some(want_bak_path.clone()))
|
Ok(Some(want_bak_path.clone()))
|
||||||
);
|
);
|
||||||
let got_data = String::from_utf8(std::fs::read(want_bak_path).unwrap()).unwrap();
|
let got_data = String::from_utf8(std::fs::read(want_bak_path).unwrap()).unwrap();
|
||||||
@ -409,7 +435,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_backup_no_file() {
|
fn test_backup_no_file() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let bak_path = backup(&dir.path().join("history.dat")).unwrap();
|
let bak_path = backup(&dir.path().join("history.dat"), Span::test_data()).unwrap();
|
||||||
assert!(bak_path.is_none());
|
assert!(bak_path.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crossterm::{
|
|||||||
event::Event, event::KeyCode, event::KeyEvent, execute, terminal, QueueableCommand,
|
event::Event, event::KeyCode, event::KeyEvent, execute, terminal, QueueableCommand,
|
||||||
};
|
};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -39,7 +40,13 @@ impl Command for KeybindingsListen {
|
|||||||
match print_events(engine_state) {
|
match print_events(engine_state) {
|
||||||
Ok(v) => Ok(v.into_pipeline_data()),
|
Ok(v) => Ok(v.into_pipeline_data()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not disable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "Error with input".into(),
|
error: "Error with input".into(),
|
||||||
msg: "".into(),
|
msg: "".into(),
|
||||||
@ -63,8 +70,20 @@ impl Command for KeybindingsListen {
|
|||||||
pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
stdout().flush()?;
|
stdout().flush().map_err(|err| {
|
||||||
terminal::enable_raw_mode()?;
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not flush stdout",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
terminal::enable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not enable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if config.use_kitty_protocol {
|
if config.use_kitty_protocol {
|
||||||
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
||||||
@ -94,7 +113,9 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let event = crossterm::event::read()?;
|
let event = crossterm::event::read().map_err(|err| {
|
||||||
|
IoError::new_internal(err.kind(), "Could not read event", nu_protocol::location!())
|
||||||
|
})?;
|
||||||
if event == Event::Key(KeyCode::Esc.into()) {
|
if event == Event::Key(KeyCode::Esc.into()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -113,9 +134,25 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
|
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
};
|
};
|
||||||
stdout.queue(crossterm::style::Print(o))?;
|
stdout.queue(crossterm::style::Print(o)).map_err(|err| {
|
||||||
stdout.queue(crossterm::style::Print("\r\n"))?;
|
IoError::new_internal(
|
||||||
stdout.flush()?;
|
err.kind(),
|
||||||
|
"Could not print output record",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
stdout
|
||||||
|
.queue(crossterm::style::Print("\r\n"))
|
||||||
|
.map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not print linebreak",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
stdout.flush().map_err(|err| {
|
||||||
|
IoError::new_internal(err.kind(), "Could not flush", nu_protocol::location!())
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.use_kitty_protocol {
|
if config.use_kitty_protocol {
|
||||||
@ -125,7 +162,13 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not disable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Value::nothing(Span::unknown()))
|
Ok(Value::nothing(Span::unknown()))
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ pub struct SemanticSuggestion {
|
|||||||
pub enum SuggestionKind {
|
pub enum SuggestionKind {
|
||||||
Command(nu_protocol::engine::CommandType),
|
Command(nu_protocol::engine::CommandType),
|
||||||
Type(nu_protocol::Type),
|
Type(nu_protocol::Type),
|
||||||
|
Module,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Suggestion> for SemanticSuggestion {
|
impl From<Suggestion> for SemanticSuggestion {
|
||||||
|
@ -43,7 +43,7 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
let paths = working_set.permanent_state.get_env_var_insensitive("path");
|
let paths = working_set.permanent_state.get_env_var_insensitive("path");
|
||||||
|
|
||||||
if let Some(paths) = paths {
|
if let Some((_, paths)) = paths {
|
||||||
if let Ok(paths) = paths.as_list() {
|
if let Ok(paths) = paths.as_list() {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let path = path.coerce_str().unwrap_or_default();
|
let path = path.coerce_str().unwrap_or_default();
|
||||||
|
@ -2,6 +2,7 @@ use crate::completions::{
|
|||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
|
use log::debug;
|
||||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_pipeline_element, parse, FlatShape};
|
use nu_parser::{flatten_pipeline_element, parse, FlatShape};
|
||||||
@ -52,6 +53,11 @@ impl NuCompleter {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"process_completion: prefix: {}, new_span: {new_span:?}, offset: {offset}, pos: {pos}",
|
||||||
|
String::from_utf8_lossy(prefix)
|
||||||
|
);
|
||||||
|
|
||||||
completer.fetch(
|
completer.fetch(
|
||||||
working_set,
|
working_set,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
@ -99,18 +105,24 @@ impl NuCompleter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match result.and_then(|data| data.into_value(span)) {
|
match result.and_then(|data| data.into_value(span)) {
|
||||||
Ok(value) => {
|
Ok(Value::List { vals, .. }) => {
|
||||||
if let Value::List { vals, .. } = value {
|
|
||||||
let result =
|
let result =
|
||||||
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
||||||
|
Some(result)
|
||||||
return Some(result);
|
}
|
||||||
|
Ok(Value::Nothing { .. }) => None,
|
||||||
|
Ok(value) => {
|
||||||
|
log::error!(
|
||||||
|
"External completer returned invalid value of type {}",
|
||||||
|
value.get_type().to_string()
|
||||||
|
);
|
||||||
|
Some(vec![])
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to eval completer block: {err}");
|
||||||
|
Some(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => println!("failed to eval completer block: {err}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
||||||
@ -128,9 +140,29 @@ impl NuCompleter {
|
|||||||
|
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
let outermost_block = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
||||||
|
|
||||||
for pipeline in &output.pipelines {
|
// Try to get the innermost block parsed (by span) so that we consider the correct context/scope.
|
||||||
|
let target_block = working_set
|
||||||
|
.delta
|
||||||
|
.blocks
|
||||||
|
.iter()
|
||||||
|
.filter_map(|block| match block.span {
|
||||||
|
Some(span) if span.contains(pos) => Some((block, span)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.reduce(|prev, cur| {
|
||||||
|
// |(block, span), (block, span)|
|
||||||
|
match cur.1.start.cmp(&prev.1.start) {
|
||||||
|
core::cmp::Ordering::Greater => cur,
|
||||||
|
core::cmp::Ordering::Equal if cur.1.end < prev.1.end => cur,
|
||||||
|
_ => prev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(block, _)| block)
|
||||||
|
.unwrap_or(&outermost_block);
|
||||||
|
|
||||||
|
for pipeline in &target_block.pipelines {
|
||||||
for pipeline_element in &pipeline.elements {
|
for pipeline_element in &pipeline.elements {
|
||||||
let flattened = flatten_pipeline_element(&working_set, pipeline_element);
|
let flattened = flatten_pipeline_element(&working_set, pipeline_element);
|
||||||
let mut spans: Vec<String> = vec![];
|
let mut spans: Vec<String> = vec![];
|
||||||
@ -140,10 +172,10 @@ impl NuCompleter {
|
|||||||
.first()
|
.first()
|
||||||
.filter(|content| content.as_str() == "sudo" || content.as_str() == "doas")
|
.filter(|content| content.as_str() == "sudo" || content.as_str() == "doas")
|
||||||
.is_some();
|
.is_some();
|
||||||
// Read the current spam to string
|
|
||||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
|
||||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
|
||||||
|
|
||||||
|
// Read the current span to string
|
||||||
|
let current_span = working_set.get_span_contents(flat.0);
|
||||||
|
let current_span_str = String::from_utf8_lossy(current_span);
|
||||||
let is_last_span = pos >= flat.0.start && pos < flat.0.end;
|
let is_last_span = pos >= flat.0.start && pos < flat.0.end;
|
||||||
|
|
||||||
// Skip the last 'a' as span item
|
// Skip the last 'a' as span item
|
||||||
@ -170,9 +202,8 @@ impl NuCompleter {
|
|||||||
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
||||||
|
|
||||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
let mut prefix = working_set.get_span_contents(flat.0);
|
|
||||||
let index = pos - flat.0.start;
|
let index = pos - flat.0.start;
|
||||||
prefix = &prefix[..index];
|
let prefix = ¤t_span[..index];
|
||||||
|
|
||||||
// Variables completion
|
// Variables completion
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
@ -319,6 +350,7 @@ impl NuCompleter {
|
|||||||
self.stack.clone(),
|
self.stack.clone(),
|
||||||
*decl_id,
|
*decl_id,
|
||||||
initial_line,
|
initial_line,
|
||||||
|
FileCompletion::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
|
@ -65,7 +65,7 @@ fn complete_rec(
|
|||||||
|
|
||||||
for entry in result.filter_map(|e| e.ok()) {
|
for entry in result.filter_map(|e| e.ok()) {
|
||||||
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
let entry_isdir = entry.path().is_dir();
|
let entry_isdir = entry.path().is_dir() && !entry.path().is_symlink();
|
||||||
let mut built = built.clone();
|
let mut built = built.clone();
|
||||||
built.parts.push(entry_name.clone());
|
built.parts.push(entry_name.clone());
|
||||||
built.isdir = entry_isdir;
|
built.isdir = entry_isdir;
|
||||||
@ -157,7 +157,6 @@ pub struct FileSuggestion {
|
|||||||
pub span: nu_protocol::Span,
|
pub span: nu_protocol::Span,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub style: Option<Style>,
|
pub style: Option<Style>,
|
||||||
pub cwd: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
@ -196,7 +195,7 @@ pub fn complete_item(
|
|||||||
.map(|cwd| Path::new(cwd.as_ref()).to_path_buf())
|
.map(|cwd| Path::new(cwd.as_ref()).to_path_buf())
|
||||||
.collect();
|
.collect();
|
||||||
let ls_colors = (engine_state.config.completions.use_ls_colors
|
let ls_colors = (engine_state.config.completions.use_ls_colors
|
||||||
&& engine_state.config.use_ansi_coloring)
|
&& engine_state.config.use_ansi_coloring.get(engine_state))
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||||
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
|
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
|
||||||
@ -261,7 +260,6 @@ pub fn complete_item(
|
|||||||
if should_collapse_dots {
|
if should_collapse_dots {
|
||||||
p = collapse_ndots(p);
|
p = collapse_ndots(p);
|
||||||
}
|
}
|
||||||
let cwd = p.cwd.clone();
|
|
||||||
let path = original_cwd.apply(p, path_separator);
|
let path = original_cwd.apply(p, path_separator);
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(
|
lsc.style_for_path_with_metadata(
|
||||||
@ -277,7 +275,6 @@ pub fn complete_item(
|
|||||||
span,
|
span,
|
||||||
path: escape_path(path, want_directory),
|
path: escape_path(path, want_directory),
|
||||||
style,
|
style,
|
||||||
cwd,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -286,8 +283,9 @@ pub fn complete_item(
|
|||||||
// Fix files or folders with quotes or hashes
|
// Fix files or folders with quotes or hashes
|
||||||
pub fn escape_path(path: String, dir: bool) -> String {
|
pub fn escape_path(path: String, dir: bool) -> String {
|
||||||
// make glob pattern have the highest priority.
|
// make glob pattern have the highest priority.
|
||||||
let glob_contaminated = path.contains(['[', '*', ']', '?']);
|
if nu_glob::is_glob(path.as_str()) {
|
||||||
if glob_contaminated {
|
let pathbuf = nu_path::expand_tilde(path);
|
||||||
|
let path = pathbuf.to_string_lossy();
|
||||||
return if path.contains('\'') {
|
return if path.contains('\'') {
|
||||||
// decide to use double quote, also need to escape `"` in path
|
// decide to use double quote, also need to escape `"` in path
|
||||||
// or else users can't do anything with completed path either.
|
// or else users can't do anything with completed path either.
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
use nucleo_matcher::{
|
||||||
|
pattern::{Atom, AtomKind, CaseMatching, Normalization},
|
||||||
|
Config, Matcher, Utf32Str,
|
||||||
|
};
|
||||||
use std::{borrow::Cow, fmt::Display};
|
use std::{borrow::Cow, fmt::Display};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::SemanticSuggestion;
|
||||||
@ -34,9 +37,10 @@ enum State<T> {
|
|||||||
items: Vec<(String, T)>,
|
items: Vec<(String, T)>,
|
||||||
},
|
},
|
||||||
Fuzzy {
|
Fuzzy {
|
||||||
matcher: Box<SkimMatcherV2>,
|
matcher: Matcher,
|
||||||
|
atom: Atom,
|
||||||
/// Holds (haystack, item, score)
|
/// Holds (haystack, item, score)
|
||||||
items: Vec<(String, T, i64)>,
|
items: Vec<(String, T, u16)>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,30 +50,38 @@ impl<T> NuMatcher<T> {
|
|||||||
///
|
///
|
||||||
/// * `needle` - The text to search for
|
/// * `needle` - The text to search for
|
||||||
pub fn new(needle: impl AsRef<str>, options: CompletionOptions) -> NuMatcher<T> {
|
pub fn new(needle: impl AsRef<str>, options: CompletionOptions) -> NuMatcher<T> {
|
||||||
let orig_needle = trim_quotes_str(needle.as_ref());
|
let needle = trim_quotes_str(needle.as_ref());
|
||||||
let lowercase_needle = if options.case_sensitive {
|
|
||||||
orig_needle.to_owned()
|
|
||||||
} else {
|
|
||||||
orig_needle.to_folded_case()
|
|
||||||
};
|
|
||||||
match options.match_algorithm {
|
match options.match_algorithm {
|
||||||
MatchAlgorithm::Prefix => NuMatcher {
|
MatchAlgorithm::Prefix => {
|
||||||
options,
|
let lowercase_needle = if options.case_sensitive {
|
||||||
needle: lowercase_needle,
|
needle.to_owned()
|
||||||
state: State::Prefix { items: Vec::new() },
|
|
||||||
},
|
|
||||||
MatchAlgorithm::Fuzzy => {
|
|
||||||
let mut matcher = SkimMatcherV2::default();
|
|
||||||
if options.case_sensitive {
|
|
||||||
matcher = matcher.respect_case();
|
|
||||||
} else {
|
} else {
|
||||||
matcher = matcher.ignore_case();
|
needle.to_folded_case()
|
||||||
};
|
};
|
||||||
NuMatcher {
|
NuMatcher {
|
||||||
options,
|
options,
|
||||||
needle: orig_needle.to_owned(),
|
needle: lowercase_needle,
|
||||||
|
state: State::Prefix { items: Vec::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatchAlgorithm::Fuzzy => {
|
||||||
|
let atom = Atom::new(
|
||||||
|
needle,
|
||||||
|
if options.case_sensitive {
|
||||||
|
CaseMatching::Respect
|
||||||
|
} else {
|
||||||
|
CaseMatching::Ignore
|
||||||
|
},
|
||||||
|
Normalization::Smart,
|
||||||
|
AtomKind::Fuzzy,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
NuMatcher {
|
||||||
|
options,
|
||||||
|
needle: needle.to_owned(),
|
||||||
state: State::Fuzzy {
|
state: State::Fuzzy {
|
||||||
matcher: Box::new(matcher),
|
matcher: Matcher::new(Config::DEFAULT),
|
||||||
|
atom,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -102,8 +114,15 @@ impl<T> NuMatcher<T> {
|
|||||||
}
|
}
|
||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
State::Fuzzy { items, matcher } => {
|
State::Fuzzy {
|
||||||
let Some(score) = matcher.fuzzy_match(haystack, &self.needle) else {
|
matcher,
|
||||||
|
atom,
|
||||||
|
items,
|
||||||
|
} => {
|
||||||
|
let mut haystack_buf = Vec::new();
|
||||||
|
let haystack_utf32 = Utf32Str::new(trim_quotes_str(haystack), &mut haystack_buf);
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
let Some(score) = atom.indices(haystack_utf32, matcher, &mut indices) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
@ -274,4 +293,22 @@ mod test {
|
|||||||
// Sort by score, then in alphabetical order
|
// Sort by score, then in alphabetical order
|
||||||
assert_eq!(vec!["fob", "foo bar", "foo/bar"], matcher.results());
|
assert_eq!(vec!["fob", "foo bar", "foo/bar"], matcher.results());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_algorithm_fuzzy_sort_strip() {
|
||||||
|
let options = CompletionOptions {
|
||||||
|
match_algorithm: MatchAlgorithm::Fuzzy,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut matcher = NuMatcher::new("'love spaces' ", options);
|
||||||
|
for item in [
|
||||||
|
"'i love spaces'",
|
||||||
|
"'i love spaces' so much",
|
||||||
|
"'lovespaces' ",
|
||||||
|
] {
|
||||||
|
matcher.add(item, item);
|
||||||
|
}
|
||||||
|
// Make sure the spaces are respected
|
||||||
|
assert_eq!(vec!["'i love spaces' so much"], matcher.results());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,32 +12,34 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use super::completion_options::NuMatcher;
|
use super::completion_options::NuMatcher;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion<T: Completer> {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
decl_id: DeclId,
|
decl_id: DeclId,
|
||||||
line: String,
|
line: String,
|
||||||
|
fallback: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomCompletion {
|
impl<T: Completer> CustomCompletion<T> {
|
||||||
pub fn new(stack: Stack, decl_id: DeclId, line: String) -> Self {
|
pub fn new(stack: Stack, decl_id: DeclId, line: String, fallback: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack,
|
stack,
|
||||||
decl_id,
|
decl_id,
|
||||||
line,
|
line,
|
||||||
|
fallback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CustomCompletion {
|
impl<T: Completer> Completer for CustomCompletion<T> {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
completion_options: &CompletionOptions,
|
orig_options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Line position
|
// Line position
|
||||||
let line_pos = pos - offset;
|
let line_pos = pos - offset;
|
||||||
@ -66,13 +68,12 @@ impl Completer for CustomCompletion {
|
|||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut custom_completion_options = None;
|
let mut completion_options = orig_options.clone();
|
||||||
let mut should_sort = true;
|
let mut should_sort = true;
|
||||||
|
|
||||||
// Parse result
|
// Parse result
|
||||||
let suggestions = result
|
let suggestions = match result.and_then(|data| data.into_value(span)) {
|
||||||
.and_then(|data| data.into_value(span))
|
Ok(value) => match &value {
|
||||||
.map(|value| match &value {
|
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
let completions = val
|
let completions = val
|
||||||
.get("completions")
|
.get("completions")
|
||||||
@ -89,36 +90,55 @@ impl Completer for CustomCompletion {
|
|||||||
should_sort = sort;
|
should_sort = sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_completion_options = Some(CompletionOptions {
|
if let Some(case_sensitive) = options
|
||||||
case_sensitive: options
|
|
||||||
.get("case_sensitive")
|
.get("case_sensitive")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(true),
|
{
|
||||||
positional: options
|
completion_options.case_sensitive = case_sensitive;
|
||||||
.get("positional")
|
}
|
||||||
.and_then(|val| val.as_bool().ok())
|
if let Some(positional) =
|
||||||
.unwrap_or(completion_options.positional),
|
options.get("positional").and_then(|val| val.as_bool().ok())
|
||||||
match_algorithm: match options.get("completion_algorithm") {
|
{
|
||||||
Some(option) => option
|
completion_options.positional = positional;
|
||||||
.coerce_string()
|
}
|
||||||
.ok()
|
if let Some(algorithm) = options
|
||||||
|
.get("completion_algorithm")
|
||||||
|
.and_then(|option| option.coerce_string().ok())
|
||||||
.and_then(|option| option.try_into().ok())
|
.and_then(|option| option.try_into().ok())
|
||||||
.unwrap_or(completion_options.match_algorithm),
|
{
|
||||||
None => completion_options.match_algorithm,
|
completion_options.match_algorithm = algorithm;
|
||||||
},
|
}
|
||||||
sort: completion_options.sort,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
Value::Nothing { .. } => {
|
||||||
})
|
return self.fallback.fetch(
|
||||||
.unwrap_or_default();
|
working_set,
|
||||||
|
stack,
|
||||||
|
prefix,
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
orig_options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::error!(
|
||||||
|
"Custom completer returned invalid value of type {}",
|
||||||
|
value.get_type().to_string()
|
||||||
|
);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error getting custom completions: {e}");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let options = custom_completion_options.unwrap_or(completion_options.clone());
|
let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), completion_options);
|
||||||
let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options);
|
|
||||||
|
|
||||||
if should_sort {
|
if should_sort {
|
||||||
for sugg in suggestions {
|
for sugg in suggestions {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
||||||
|
use nu_path::expand_tilde;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
use std::path::{is_separator, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::{SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DotNuCompletion {}
|
pub struct DotNuCompletion {}
|
||||||
@ -28,102 +29,105 @@ impl Completer for DotNuCompletion {
|
|||||||
_pos: usize,
|
_pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
|
let prefix_str = String::from_utf8_lossy(prefix);
|
||||||
let mut search_dirs: Vec<String> = vec![];
|
let start_with_backquote = prefix_str.starts_with('`');
|
||||||
|
let end_with_backquote = prefix_str.ends_with('`');
|
||||||
|
let prefix_str = prefix_str.replace('`', "");
|
||||||
|
let mut search_dirs: Vec<PathBuf> = vec![];
|
||||||
|
|
||||||
// If prefix_str is only a word we want to search in the current dir
|
// If prefix_str is only a word we want to search in the current dir
|
||||||
let (base, partial) = prefix_str
|
let (base, partial) = prefix_str
|
||||||
.rsplit_once(is_separator)
|
.rsplit_once(is_separator)
|
||||||
.unwrap_or((".", &prefix_str));
|
.unwrap_or((".", &prefix_str));
|
||||||
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
|
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
|
||||||
let mut partial = partial.to_string();
|
|
||||||
// On windows, this standardizes paths to use \
|
|
||||||
let mut is_current_folder = false;
|
|
||||||
|
|
||||||
// Fetch the lib dirs
|
// Fetch the lib dirs
|
||||||
let lib_dirs: Vec<String> = if let Some(lib_dirs) = working_set.get_env_var("NU_LIB_DIRS") {
|
let lib_dirs: Vec<PathBuf> = working_set
|
||||||
|
.find_variable(b"$NU_LIB_DIRS")
|
||||||
|
.and_then(|vid| working_set.get_variable(vid).const_val.as_ref())
|
||||||
|
.or(working_set.get_env_var("NU_LIB_DIRS"))
|
||||||
|
.map(|lib_dirs| {
|
||||||
lib_dirs
|
lib_dirs
|
||||||
.as_list()
|
.as_list()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|it| it.iter().filter_map(|x| x.to_path().ok()))
|
||||||
it.iter().map(|x| {
|
.map(expand_tilde)
|
||||||
x.to_path()
|
|
||||||
.expect("internal error: failed to convert lib path")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.map(|it| {
|
|
||||||
it.into_os_string()
|
|
||||||
.into_string()
|
|
||||||
.expect("internal error: failed to convert OS path")
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
})
|
||||||
vec![]
|
.unwrap_or_default();
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the base_dir is a folder
|
// Check if the base_dir is a folder
|
||||||
// rsplit_once removes the separator
|
// rsplit_once removes the separator
|
||||||
|
let cwd = working_set.permanent_state.cwd(None);
|
||||||
if base_dir != "." {
|
if base_dir != "." {
|
||||||
// Add the base dir into the directories to be searched
|
// Search in base_dir as well as lib_dirs
|
||||||
search_dirs.push(base_dir.clone());
|
if let Ok(mut cwd) = cwd {
|
||||||
|
cwd.push(&base_dir);
|
||||||
// Reset the partial adding the basic dir back
|
search_dirs.push(cwd.into_std_path_buf());
|
||||||
// in order to make the span replace work properly
|
}
|
||||||
let mut base_dir_partial = base_dir;
|
search_dirs.extend(lib_dirs.into_iter().map(|mut dir| {
|
||||||
base_dir_partial.push_str(&partial);
|
dir.push(&base_dir);
|
||||||
|
dir
|
||||||
partial = base_dir_partial;
|
}));
|
||||||
} else {
|
} else {
|
||||||
// Fetch the current folder
|
if let Ok(cwd) = cwd {
|
||||||
#[allow(deprecated)]
|
search_dirs.push(cwd.into_std_path_buf());
|
||||||
let current_folder = working_set.permanent_state.current_work_dir();
|
}
|
||||||
is_current_folder = true;
|
|
||||||
|
|
||||||
// Add the current folder and the lib dirs into the
|
|
||||||
// directories to be searched
|
|
||||||
search_dirs.push(current_folder);
|
|
||||||
search_dirs.extend(lib_dirs);
|
search_dirs.extend(lib_dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the files filtering the ones that ends with .nu
|
// Fetch the files filtering the ones that ends with .nu
|
||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
|
|
||||||
let completions = file_path_completion(
|
let completions = file_path_completion(
|
||||||
span,
|
span,
|
||||||
&partial,
|
partial,
|
||||||
&search_dirs.iter().map(|d| d.as_str()).collect::<Vec<_>>(),
|
&search_dirs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| d.to_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
options,
|
options,
|
||||||
working_set.permanent_state,
|
working_set.permanent_state,
|
||||||
stack,
|
stack,
|
||||||
);
|
);
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(move |it| {
|
|
||||||
// Different base dir, so we list the .nu files or folders
|
// Different base dir, so we list the .nu files or folders
|
||||||
if !is_current_folder {
|
.filter(|it| {
|
||||||
it.path.ends_with(".nu") || it.path.ends_with(SEP)
|
// for paths with spaces in them
|
||||||
} else {
|
let path = it.path.trim_end_matches('`');
|
||||||
// Lib dirs, so we filter only the .nu files or directory modules
|
path.ends_with(".nu") || path.ends_with(SEP)
|
||||||
if it.path.ends_with(SEP) {
|
|
||||||
Path::new(&it.cwd).join(&it.path).join("mod.nu").exists()
|
|
||||||
} else {
|
|
||||||
it.path.ends_with(".nu")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(move |x| SemanticSuggestion {
|
.map(|x| {
|
||||||
|
let append_whitespace =
|
||||||
|
x.path.ends_with(".nu") && (!start_with_backquote || end_with_backquote);
|
||||||
|
// Re-calculate the span to replace
|
||||||
|
let mut span_offset = 0;
|
||||||
|
let mut value = x.path.to_string();
|
||||||
|
// Complete only the last path component
|
||||||
|
if base_dir != "." {
|
||||||
|
span_offset = base_dir.len() + 1
|
||||||
|
}
|
||||||
|
// Retain only one '`'
|
||||||
|
if start_with_backquote {
|
||||||
|
value = value.trim_start_matches('`').to_string();
|
||||||
|
span_offset += 1;
|
||||||
|
}
|
||||||
|
// Add the backquote back
|
||||||
|
if end_with_backquote && !value.ends_with('`') {
|
||||||
|
value.push('`');
|
||||||
|
}
|
||||||
|
let end = x.span.end - offset;
|
||||||
|
let start = std::cmp::min(end, x.span.start - offset + span_offset);
|
||||||
|
SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.path,
|
value,
|
||||||
style: x.style,
|
style: x.style,
|
||||||
span: reedline::Span {
|
span: reedline::Span { start, end },
|
||||||
start: x.span.start - offset,
|
append_whitespace,
|
||||||
end: x.span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
kind: Some(SuggestionKind::Module),
|
||||||
kind: None,
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
@ -107,10 +107,14 @@ impl Completer for OperatorCompletion {
|
|||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
("not-in", "Is not a member of (doesn't use regex)"),
|
||||||
],
|
],
|
||||||
Expr::FullCellPath(path) => match path.head.expr {
|
Expr::FullCellPath(path) => match path.head.expr {
|
||||||
Expr::List(_) => vec![(
|
Expr::List(_) => vec![
|
||||||
|
(
|
||||||
"++",
|
"++",
|
||||||
"Concatenates two lists, two strings, or two binary values",
|
"Concatenates two lists, two strings, or two binary values",
|
||||||
)],
|
),
|
||||||
|
("has", "Contains a value of (doesn't use regex)"),
|
||||||
|
("not-has", "Does not contain a value of (doesn't use regex)"),
|
||||||
|
],
|
||||||
Expr::Var(id) => get_variable_completions(id, working_set),
|
Expr::Var(id) => get_variable_completions(id, working_set),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
},
|
},
|
||||||
|
@ -18,7 +18,7 @@ const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
|||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||||
use nu_protocol::ShellError;
|
use nu_protocol::{shell_error::io::IoError, ShellError};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let span = plugin_file.as_ref().map(|s| s.span);
|
let span = plugin_file.as_ref().map(|s| s.span);
|
||||||
@ -49,7 +49,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
"add plugin file to engine_state",
|
"add plugin file to engine_state",
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
@ -75,16 +78,12 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
} else {
|
} else {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::Io(IoError::new_internal_with_path(
|
||||||
error: format!(
|
err.kind(),
|
||||||
"Error while opening plugin registry file: {}",
|
"Could not open plugin registry file",
|
||||||
plugin_path.display()
|
nu_protocol::location!(),
|
||||||
),
|
plugin_path,
|
||||||
msg: "plugin path defined here".into(),
|
)),
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
inner: vec![err.into()],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -129,7 +128,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("read plugin file {}", plugin_path.display()),
|
&format!("read plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
@ -145,7 +147,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("load plugin file {}", plugin_path.display()),
|
&format!("load plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,8 +230,8 @@ pub fn eval_config_contents(
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
|
shell_error::io::IoError, PluginExample, PluginIdentity, PluginRegistryItem,
|
||||||
ShellError,
|
PluginRegistryItemData, PluginSignature, ShellError,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@ -315,7 +320,15 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
|||||||
// Write the new file
|
// Write the new file
|
||||||
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
|
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
|
||||||
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
|
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
|
||||||
.map_err(|e| e.into())
|
.map_err(|err| {
|
||||||
|
IoError::new_internal_with_path(
|
||||||
|
err.kind(),
|
||||||
|
"Could not create new plugin file",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
new_plugin_file_path.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map_err(ShellError::from)
|
||||||
.and_then(|file| contents.write_to(file, None))
|
.and_then(|file| contents.write_to(file, None))
|
||||||
{
|
{
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
@ -345,7 +358,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
|||||||
perf!(
|
perf!(
|
||||||
"migrate old plugin file",
|
"migrate old plugin file",
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(&engine_state)
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
cli_error::report_compile_error,
|
cli_error::report_compile_error,
|
||||||
@ -50,9 +50,6 @@ pub fn evaluate_commands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
convert_env_values(engine_state, stack)?;
|
|
||||||
|
|
||||||
// Parse the source code
|
// Parse the source code
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
use crate::util::{eval_source, print_pipeline};
|
use crate::util::{eval_source, print_pipeline};
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
cli_error::report_compile_error,
|
cli_error::report_compile_error,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_parse_error, report_parse_warning, PipelineData, ShellError, Span, Value,
|
report_parse_error, report_parse_warning,
|
||||||
|
shell_error::io::IoError,
|
||||||
|
PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
/// Entry point for evaluating a file.
|
/// Entry point for evaluating a file.
|
||||||
///
|
///
|
||||||
@ -22,15 +24,15 @@ pub fn evaluate_file(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
// Convert environment variables from Strings to Values and store them in the engine state.
|
|
||||||
convert_env_values(engine_state, stack)?;
|
|
||||||
|
|
||||||
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
||||||
|
|
||||||
let file_path =
|
let file_path = canonicalize_with(&path, cwd).map_err(|err| {
|
||||||
canonicalize_with(&path, cwd).map_err(|err| ShellError::FileNotFoundCustom {
|
IoError::new_with_additional_context(
|
||||||
msg: format!("Could not access file '{path}': {err}"),
|
err.kind(),
|
||||||
span: Span::unknown(),
|
Span::unknown(),
|
||||||
|
PathBuf::from(&path),
|
||||||
|
"Could not access file",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_path_str = file_path
|
let file_path_str = file_path
|
||||||
@ -43,17 +45,23 @@ pub fn evaluate_file(
|
|||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file = std::fs::read(&file_path).map_err(|err| ShellError::FileNotFoundCustom {
|
let file = std::fs::read(&file_path).map_err(|err| {
|
||||||
msg: format!("Could not read file '{file_path_str}': {err}"),
|
IoError::new_with_additional_context(
|
||||||
span: Span::unknown(),
|
err.kind(),
|
||||||
|
Span::unknown(),
|
||||||
|
file_path.clone(),
|
||||||
|
"Could not read file",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
engine_state.file = Some(file_path.clone());
|
engine_state.file = Some(file_path.clone());
|
||||||
|
|
||||||
let parent = file_path
|
let parent = file_path.parent().ok_or_else(|| {
|
||||||
.parent()
|
IoError::new_with_additional_context(
|
||||||
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
std::io::ErrorKind::NotFound,
|
||||||
msg: format!("The file path '{file_path_str}' does not have a parent"),
|
Span::unknown(),
|
||||||
span: Span::unknown(),
|
file_path.clone(),
|
||||||
|
"The file path does not have a parent",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
|
@ -87,14 +87,6 @@ fn get_prompt_string(
|
|||||||
x.insert_str(0, "\x1b[0m")
|
x.insert_str(0, "\x1b[0m")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Just remove the very last newline.
|
|
||||||
if x.ends_with('\n') {
|
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.ends_with('\r') {
|
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
x
|
x
|
||||||
});
|
});
|
||||||
// Let's keep this for debugging purposes with nu --log-level warn
|
// Let's keep this for debugging purposes with nu --log-level warn
|
||||||
|
@ -19,8 +19,9 @@ use miette::{ErrReport, IntoDiagnostic, Result};
|
|||||||
use nu_cmd_base::util::get_editor;
|
use nu_cmd_base::util::get_editor;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{convert_env_values, current_dir_str, env_to_strings};
|
use nu_engine::env_to_strings;
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
@ -61,9 +62,7 @@ pub fn evaluate_repl(
|
|||||||
// from the Arc. This lets us avoid copying stack variables needlessly
|
// from the Arc. This lets us avoid copying stack variables needlessly
|
||||||
let mut unique_stack = stack.clone();
|
let mut unique_stack = stack.clone();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let use_color = config.use_ansi_coloring;
|
let use_color = config.use_ansi_coloring.get(engine_state);
|
||||||
|
|
||||||
confirm_stdin_is_terminal()?;
|
|
||||||
|
|
||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
|
||||||
@ -81,13 +80,6 @@ pub fn evaluate_repl(
|
|||||||
stack.clone(),
|
stack.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
|
||||||
report_shell_error(engine_state, &e);
|
|
||||||
}
|
|
||||||
perf!("translate env vars", start_time, use_color);
|
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
unique_stack.add_env_var(
|
unique_stack.add_env_var(
|
||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
@ -111,6 +103,8 @@ pub fn evaluate_repl(
|
|||||||
engine_state.merge_env(&mut unique_stack)?;
|
engine_state.merge_env(&mut unique_stack)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirm_stdin_is_terminal()?;
|
||||||
|
|
||||||
let hostname = System::host_name();
|
let hostname = System::host_name();
|
||||||
if shell_integration_osc2 {
|
if shell_integration_osc2 {
|
||||||
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
|
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
|
||||||
@ -146,7 +140,20 @@ pub fn evaluate_repl(
|
|||||||
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
if load_std_lib.is_none() {
|
||||||
|
match engine_state.get_config().show_banner {
|
||||||
|
Value::Bool { val: false, .. } => {}
|
||||||
|
Value::String { ref val, .. } if val == "short" => {
|
||||||
|
eval_source(
|
||||||
|
engine_state,
|
||||||
|
&mut unique_stack,
|
||||||
|
r#"banner --short"#.as_bytes(),
|
||||||
|
"show short banner",
|
||||||
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut unique_stack,
|
&mut unique_stack,
|
||||||
@ -156,6 +163,8 @@ pub fn evaluate_repl(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kitty_protocol_healthcheck(engine_state);
|
kitty_protocol_healthcheck(engine_state);
|
||||||
|
|
||||||
@ -375,7 +384,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
)))
|
)))
|
||||||
.with_quick_completions(config.completions.quick)
|
.with_quick_completions(config.completions.quick)
|
||||||
.with_partial_completions(config.completions.partial)
|
.with_partial_completions(config.completions.partial)
|
||||||
.with_ansi_colors(config.use_ansi_coloring)
|
.with_ansi_colors(config.use_ansi_coloring.get(engine_state))
|
||||||
.with_cwd(Some(
|
.with_cwd(Some(
|
||||||
engine_state
|
engine_state
|
||||||
.cwd(None)
|
.cwd(None)
|
||||||
@ -395,7 +404,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
line_editor = if config.use_ansi_coloring {
|
line_editor = if config.use_ansi_coloring.get(engine_state) {
|
||||||
line_editor.with_hinter(Box::new({
|
line_editor.with_hinter(Box::new({
|
||||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||||
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||||
@ -801,8 +810,10 @@ fn parse_operation(
|
|||||||
) -> Result<ReplOperation, ErrReport> {
|
) -> Result<ReplOperation, ErrReport> {
|
||||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||||
// Check if this is a single call to a directory, if so auto-cd
|
// Check if this is a single call to a directory, if so auto-cd
|
||||||
#[allow(deprecated)]
|
let cwd = engine_state
|
||||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack).unwrap_or_default();
|
.cwd(Some(stack))
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
let mut orig = s.clone();
|
let mut orig = s.clone();
|
||||||
if orig.starts_with('`') {
|
if orig.starts_with('`') {
|
||||||
orig = trim_quotes_str(&orig).to_string()
|
orig = trim_quotes_str(&orig).to_string()
|
||||||
@ -836,21 +847,26 @@ fn do_auto_cd(
|
|||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::DirectoryNotFound {
|
&ShellError::Io(IoError::new_with_additional_context(
|
||||||
dir: path.to_string_lossy().to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span,
|
span,
|
||||||
},
|
PathBuf::from(&path),
|
||||||
|
"Cannot change directory",
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
if let PermissionResult::PermissionDenied(_) = have_permission(path.clone()) {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::IOError {
|
&ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
std::io::ErrorKind::PermissionDenied,
|
||||||
},
|
span,
|
||||||
|
PathBuf::from(path),
|
||||||
|
"Cannot change directory",
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -961,8 +977,7 @@ fn run_shell_integration_osc2(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
use_color: bool,
|
use_color: bool,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
@ -1006,8 +1021,7 @@ fn run_shell_integration_osc7(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
use_color: bool,
|
use_color: bool,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||||
@ -1030,8 +1044,7 @@ fn run_shell_integration_osc7(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
|
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
|
||||||
@ -1055,8 +1068,7 @@ fn run_shell_integration_osc633(
|
|||||||
use_color: bool,
|
use_color: bool,
|
||||||
repl_cmd_line_text: String,
|
repl_cmd_line_text: String,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
||||||
if stack
|
if stack
|
||||||
@ -1147,7 +1159,7 @@ fn setup_history(
|
|||||||
/// Setup Reedline keybindingds based on the provided config
|
/// Setup Reedline keybindingds based on the provided config
|
||||||
///
|
///
|
||||||
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
||||||
return match create_keybindings(engine_state.get_config()) {
|
match create_keybindings(engine_state.get_config()) {
|
||||||
Ok(keybindings) => match keybindings {
|
Ok(keybindings) => match keybindings {
|
||||||
KeybindingsMode::Emacs(keybindings) => {
|
KeybindingsMode::Emacs(keybindings) => {
|
||||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||||
@ -1165,7 +1177,7 @@ fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedl
|
|||||||
report_shell_error(engine_state, &e);
|
report_shell_error(engine_state, &e);
|
||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1562,6 +1574,13 @@ mod test_auto_cd {
|
|||||||
symlink(&dir, &link).unwrap();
|
symlink(&dir, &link).unwrap();
|
||||||
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
||||||
check(tempdir, input, link);
|
check(tempdir, input, link);
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo").join("bar");
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let link = tempdir.join("link2");
|
||||||
|
symlink(&dir, &link).unwrap();
|
||||||
|
let input = "..";
|
||||||
|
check(link, input, tempdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -132,7 +132,7 @@ fn gather_env_vars(
|
|||||||
working_set.error(err);
|
working_set.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if working_set.parse_errors.first().is_some() {
|
if !working_set.parse_errors.is_empty() {
|
||||||
report_capture_error(
|
report_capture_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&String::from_utf8_lossy(contents),
|
&String::from_utf8_lossy(contents),
|
||||||
@ -176,7 +176,7 @@ fn gather_env_vars(
|
|||||||
working_set.error(err);
|
working_set.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if working_set.parse_errors.first().is_some() {
|
if !working_set.parse_errors.is_empty() {
|
||||||
report_capture_error(
|
report_capture_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&String::from_utf8_lossy(contents),
|
&String::from_utf8_lossy(contents),
|
||||||
@ -265,7 +265,10 @@ pub fn eval_source(
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("eval_source {}", &fname),
|
&format!("eval_source {}", &fname),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
|
|
||||||
exit_code
|
exit_code
|
||||||
|
@ -62,50 +62,29 @@ fn extern_completer() -> NuCompleter {
|
|||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[fixture]
|
fn custom_completer_with_options(
|
||||||
fn completer_strings_with_options() -> NuCompleter {
|
global_opts: &str,
|
||||||
// Create a new engine
|
completer_opts: &str,
|
||||||
|
completions: &[&str],
|
||||||
|
) -> NuCompleter {
|
||||||
let (_, _, mut engine, mut stack) = new_engine();
|
let (_, _, mut engine, mut stack) = new_engine();
|
||||||
// Add record value as example
|
let command = format!(
|
||||||
let record = r#"
|
r#"
|
||||||
# To test that the config setting has no effect on the custom completions
|
{}
|
||||||
$env.config.completions.algorithm = "fuzzy"
|
def comp [] {{
|
||||||
def animals [] {
|
{{ completions: [{}], options: {{ {} }} }}
|
||||||
{
|
}}
|
||||||
# Very rare and totally real animals
|
def my-command [arg: string@comp] {{}}"#,
|
||||||
completions: ["Abcdef", "Foo Abcdef", "Acd Bar" ],
|
global_opts,
|
||||||
options: {
|
completions
|
||||||
completion_algorithm: "prefix",
|
.iter()
|
||||||
positional: false,
|
.map(|comp| format!("'{}'", comp))
|
||||||
case_sensitive: false,
|
.collect::<Vec<_>>()
|
||||||
}
|
.join(", "),
|
||||||
}
|
completer_opts,
|
||||||
}
|
);
|
||||||
def my-command [animal: string@animals] { print $animal }"#;
|
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack).is_ok());
|
|
||||||
|
|
||||||
// Instantiate a new completer
|
|
||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[fixture]
|
|
||||||
fn completer_strings_no_sort() -> NuCompleter {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, mut engine, mut stack) = new_engine();
|
|
||||||
let command = r#"
|
|
||||||
def animals [] {
|
|
||||||
{
|
|
||||||
completions: ["zzzfoo", "foo", "not matched", "abcfoo" ],
|
|
||||||
options: {
|
|
||||||
completion_algorithm: "fuzzy",
|
|
||||||
sort: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def my-command [animal: string@animals] { print $animal }"#;
|
|
||||||
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
|
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||||
|
|
||||||
// Instantiate a new completer
|
|
||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,25 +111,6 @@ fn custom_completer() -> NuCompleter {
|
|||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[fixture]
|
|
||||||
fn subcommand_completer() -> NuCompleter {
|
|
||||||
// Create a new engine
|
|
||||||
let (_, _, mut engine, mut stack) = new_engine();
|
|
||||||
|
|
||||||
let commands = r#"
|
|
||||||
$env.config.completions.algorithm = "fuzzy"
|
|
||||||
def foo [] {}
|
|
||||||
def "foo bar" [] {}
|
|
||||||
def "foo abaz" [] {}
|
|
||||||
def "foo aabcrr" [] {}
|
|
||||||
def food [] {}
|
|
||||||
"#;
|
|
||||||
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack).is_ok());
|
|
||||||
|
|
||||||
// Instantiate a new completer
|
|
||||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use fuzzy completions but sort in alphabetical order
|
/// Use fuzzy completions but sort in alphabetical order
|
||||||
#[fixture]
|
#[fixture]
|
||||||
fn fuzzy_alpha_sort_completer() -> NuCompleter {
|
fn fuzzy_alpha_sort_completer() -> NuCompleter {
|
||||||
@ -176,7 +136,7 @@ fn variables_dollar_sign_with_variablecompletion() {
|
|||||||
let target_dir = "$ ";
|
let target_dir = "$ ";
|
||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
assert_eq!(8, suggestions.len());
|
assert_eq!(9, suggestions.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -217,27 +177,94 @@ fn variables_customcompletion_subcommands_with_customcompletion_2(
|
|||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
/// $env.config should be overridden by the custom completer's options
|
||||||
fn customcompletions_substring_matching(mut completer_strings_with_options: NuCompleter) {
|
#[test]
|
||||||
let suggestions = completer_strings_with_options.complete("my-command Abcd", 15);
|
fn customcompletions_override_options() {
|
||||||
|
let mut completer = custom_completer_with_options(
|
||||||
|
r#"$env.config.completions.algorithm = "fuzzy"
|
||||||
|
$env.config.completions.case_sensitive = false"#,
|
||||||
|
r#"completion_algorithm: "prefix",
|
||||||
|
positional: false,
|
||||||
|
case_sensitive: true,
|
||||||
|
sort: true"#,
|
||||||
|
&["Foo Abcdef", "Abcdef", "Acd Bar"],
|
||||||
|
);
|
||||||
|
|
||||||
|
// positional: false should make it do substring matching
|
||||||
|
// sort: true should force sorting
|
||||||
let expected: Vec<String> = vec!["Abcdef".into(), "Foo Abcdef".into()];
|
let expected: Vec<String> = vec!["Abcdef".into(), "Foo Abcdef".into()];
|
||||||
|
let suggestions = completer.complete("my-command Abcd", 15);
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
|
||||||
|
// Custom options should make case-sensitive
|
||||||
|
let suggestions = completer.complete("my-command aBcD", 15);
|
||||||
|
assert!(suggestions.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// $env.config should be inherited by the custom completer's options
|
||||||
|
#[test]
|
||||||
|
fn customcompletions_inherit_options() {
|
||||||
|
let mut completer = custom_completer_with_options(
|
||||||
|
r#"$env.config.completions.algorithm = "fuzzy"
|
||||||
|
$env.config.completions.case_sensitive = false"#,
|
||||||
|
"",
|
||||||
|
&["Foo Abcdef", "Abcdef", "Acd Bar"],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure matching is fuzzy
|
||||||
|
let suggestions = completer.complete("my-command Acd", 14);
|
||||||
|
let expected: Vec<String> = vec!["Acd Bar".into(), "Abcdef".into(), "Foo Abcdef".into()];
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
|
||||||
|
// Custom options should make matching case insensitive
|
||||||
|
let suggestions = completer.complete("my-command acd", 14);
|
||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[test]
|
||||||
fn customcompletions_case_insensitive(mut completer_strings_with_options: NuCompleter) {
|
fn customcompletions_no_sort() {
|
||||||
let suggestions = completer_strings_with_options.complete("my-command foo", 14);
|
let mut completer = custom_completer_with_options(
|
||||||
let expected: Vec<String> = vec!["Foo Abcdef".into()];
|
"",
|
||||||
match_suggestions(&expected, &suggestions);
|
r#"completion_algorithm: "fuzzy",
|
||||||
}
|
sort: false"#,
|
||||||
|
&["zzzfoo", "foo", "not matched", "abcfoo"],
|
||||||
#[rstest]
|
);
|
||||||
fn customcompletions_no_sort(mut completer_strings_no_sort: NuCompleter) {
|
let suggestions = completer.complete("my-command foo", 14);
|
||||||
let suggestions = completer_strings_no_sort.complete("my-command foo", 14);
|
|
||||||
let expected: Vec<String> = vec!["zzzfoo".into(), "foo".into(), "abcfoo".into()];
|
let expected: Vec<String> = vec!["zzzfoo".into(), "foo".into(), "abcfoo".into()];
|
||||||
match_suggestions(&expected, &suggestions);
|
match_suggestions(&expected, &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fallback to file completions if custom completer returns null
|
||||||
|
#[test]
|
||||||
|
fn customcompletions_fallback() {
|
||||||
|
let (_, _, mut engine, mut stack) = new_engine();
|
||||||
|
let command = r#"
|
||||||
|
def comp [] { null }
|
||||||
|
def my-command [arg: string@comp] {}"#;
|
||||||
|
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
let completion_str = "my-command test";
|
||||||
|
let suggestions = completer.complete(completion_str, completion_str.len());
|
||||||
|
let expected: Vec<String> = vec![folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Suppress completions for invalid values
|
||||||
|
#[test]
|
||||||
|
fn customcompletions_invalid() {
|
||||||
|
let (_, _, mut engine, mut stack) = new_engine();
|
||||||
|
let command = r#"
|
||||||
|
def comp [] { 123 }
|
||||||
|
def my-command [arg: string@comp] {}"#;
|
||||||
|
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||||
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
|
let completion_str = "my-command foo";
|
||||||
|
let suggestions = completer.complete(completion_str, completion_str.len());
|
||||||
|
assert!(suggestions.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dotnu_completions() {
|
fn dotnu_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
@ -246,6 +273,33 @@ fn dotnu_completions() {
|
|||||||
// Instantiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
|
// Test nested nu script
|
||||||
|
#[cfg(windows)]
|
||||||
|
let completion_str = "use `.\\dir_module\\".to_string();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let completion_str = "use `./dir_module/".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
match_suggestions(
|
||||||
|
&vec![
|
||||||
|
"mod.nu".into(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"sub module\\`".into(),
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
"sub module/`".into(),
|
||||||
|
],
|
||||||
|
&suggestions,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test nested nu script, with ending '`'
|
||||||
|
#[cfg(windows)]
|
||||||
|
let completion_str = "use `.\\dir_module\\sub module\\`".to_string();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let completion_str = "use `./dir_module/sub module/`".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
match_suggestions(&vec!["sub.nu`".into()], &suggestions);
|
||||||
|
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
"asdf.nu".into(),
|
"asdf.nu".into(),
|
||||||
"bar.nu".into(),
|
"bar.nu".into(),
|
||||||
@ -256,6 +310,18 @@ fn dotnu_completions() {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
"dir_module/".into(),
|
"dir_module/".into(),
|
||||||
"foo.nu".into(),
|
"foo.nu".into(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"lib-dir1\\".into(),
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
"lib-dir1/".into(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"lib-dir2\\".into(),
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
"lib-dir2/".into(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"lib-dir3\\".into(),
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
"lib-dir3/".into(),
|
||||||
"spam.nu".into(),
|
"spam.nu".into(),
|
||||||
"xyzzy.nu".into(),
|
"xyzzy.nu".into(),
|
||||||
];
|
];
|
||||||
@ -316,6 +382,27 @@ fn external_completer_pass_flags() {
|
|||||||
assert_eq!("--", suggestions.get(2).unwrap().value);
|
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fallback to file completions when external completer returns null
|
||||||
|
#[test]
|
||||||
|
fn external_completer_fallback() {
|
||||||
|
let block = "{|spans| null}";
|
||||||
|
let input = "foo test".to_string();
|
||||||
|
|
||||||
|
let expected = vec![folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Suppress completions when external completer returns invalid value
|
||||||
|
#[test]
|
||||||
|
fn external_completer_invalid() {
|
||||||
|
let block = "{|spans| 123}";
|
||||||
|
let input = "foo ".to_string();
|
||||||
|
|
||||||
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
assert!(suggestions.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn file_completions() {
|
fn file_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
@ -335,6 +422,7 @@ fn file_completions() {
|
|||||||
folder(dir.join("directory_completion")),
|
folder(dir.join("directory_completion")),
|
||||||
file(dir.join("nushell")),
|
file(dir.join("nushell")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
|
file(dir.join("test_a_symlink")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
file(dir.join(".hidden_file")),
|
file(dir.join(".hidden_file")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
@ -368,6 +456,7 @@ fn file_completions() {
|
|||||||
folder(dir.join("directory_completion")),
|
folder(dir.join("directory_completion")),
|
||||||
file(dir.join("nushell")),
|
file(dir.join("nushell")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
|
file(dir.join("test_a_symlink")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
file(dir.join(".hidden_file")),
|
file(dir.join(".hidden_file")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
@ -445,6 +534,7 @@ fn custom_command_rest_any_args_file_completions() {
|
|||||||
folder(dir.join("directory_completion")),
|
folder(dir.join("directory_completion")),
|
||||||
file(dir.join("nushell")),
|
file(dir.join("nushell")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
|
file(dir.join("test_a_symlink")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
file(dir.join(".hidden_file")),
|
file(dir.join(".hidden_file")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
@ -464,6 +554,7 @@ fn custom_command_rest_any_args_file_completions() {
|
|||||||
folder(dir.join("directory_completion")),
|
folder(dir.join("directory_completion")),
|
||||||
file(dir.join("nushell")),
|
file(dir.join("nushell")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
|
file(dir.join("test_a_symlink")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
file(dir.join(".hidden_file")),
|
file(dir.join(".hidden_file")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
@ -763,6 +854,7 @@ fn command_ls_with_filecompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -774,6 +866,7 @@ fn command_ls_with_filecompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -805,6 +898,7 @@ fn command_open_with_filecompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -816,6 +910,7 @@ fn command_open_with_filecompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -847,6 +942,7 @@ fn command_rm_with_globcompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -858,6 +954,7 @@ fn command_rm_with_globcompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -882,6 +979,7 @@ fn command_cp_with_globcompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -893,6 +991,7 @@ fn command_cp_with_globcompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -917,6 +1016,7 @@ fn command_save_with_filecompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -928,6 +1028,7 @@ fn command_save_with_filecompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -952,6 +1053,7 @@ fn command_touch_with_filecompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -963,6 +1065,7 @@ fn command_touch_with_filecompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -987,6 +1090,7 @@ fn command_watch_with_filecompletion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -998,6 +1102,7 @@ fn command_watch_with_filecompletion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -1007,24 +1112,32 @@ fn command_watch_with_filecompletion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn subcommand_completions(mut subcommand_completer: NuCompleter) {
|
fn subcommand_completions() {
|
||||||
let prefix = "foo br";
|
let (_, _, mut engine, mut stack) = new_engine();
|
||||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
let commands = r#"
|
||||||
match_suggestions(
|
$env.config.completions.algorithm = "fuzzy"
|
||||||
&vec!["foo bar".to_string(), "foo aabcrr".to_string()],
|
def foo-test-command [] {}
|
||||||
&suggestions,
|
def "foo-test-command bar" [] {}
|
||||||
);
|
def "foo-test-command aagap bcr" [] {}
|
||||||
|
def "food bar" [] {}
|
||||||
|
"#;
|
||||||
|
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||||
|
let mut subcommand_completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
let prefix = "foo b";
|
let prefix = "fod br";
|
||||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||||
match_suggestions(
|
match_suggestions(
|
||||||
&vec![
|
&vec![
|
||||||
"foo bar".to_string(),
|
"food bar".to_string(),
|
||||||
"foo abaz".to_string(),
|
"foo-test-command bar".to_string(),
|
||||||
"foo aabcrr".to_string(),
|
"foo-test-command aagap bcr".to_string(),
|
||||||
],
|
],
|
||||||
&suggestions,
|
&suggestions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let prefix = "foot bar";
|
||||||
|
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||||
|
match_suggestions(&vec!["foo-test-command bar".to_string()], &suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1339,7 +1452,7 @@ fn variables_completions() {
|
|||||||
// Test completions for $nu
|
// Test completions for $nu
|
||||||
let suggestions = completer.complete("$nu.", 4);
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
assert_eq!(18, suggestions.len());
|
assert_eq!(19, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
"cache-dir".into(),
|
"cache-dir".into(),
|
||||||
@ -1359,6 +1472,7 @@ fn variables_completions() {
|
|||||||
"plugin-path".into(),
|
"plugin-path".into(),
|
||||||
"startup-time".into(),
|
"startup-time".into(),
|
||||||
"temp-path".into(),
|
"temp-path".into(),
|
||||||
|
"user-autoload-dirs".into(),
|
||||||
"vendor-autoload-dirs".into(),
|
"vendor-autoload-dirs".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -1452,9 +1566,17 @@ fn alias_of_command_and_flags() {
|
|||||||
|
|
||||||
let suggestions = completer.complete("ll t", 4);
|
let suggestions = completer.complete("ll t", 4);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
match_suggestions(&expected_paths, &suggestions)
|
match_suggestions(&expected_paths, &suggestions)
|
||||||
}
|
}
|
||||||
@ -1471,9 +1593,17 @@ fn alias_of_basic_command() {
|
|||||||
|
|
||||||
let suggestions = completer.complete("ll t", 4);
|
let suggestions = completer.complete("ll t", 4);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
match_suggestions(&expected_paths, &suggestions)
|
match_suggestions(&expected_paths, &suggestions)
|
||||||
}
|
}
|
||||||
@ -1493,9 +1623,17 @@ fn alias_of_another_alias() {
|
|||||||
|
|
||||||
let suggestions = completer.complete("lf t", 4);
|
let suggestions = completer.complete("lf t", 4);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
match_suggestions(&expected_paths, &suggestions)
|
match_suggestions(&expected_paths, &suggestions)
|
||||||
}
|
}
|
||||||
@ -1544,6 +1682,7 @@ fn unknown_command_completion() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -1555,6 +1694,7 @@ fn unknown_command_completion() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -1606,6 +1746,7 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
"directory_completion\\".to_string(),
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
@ -1617,6 +1758,7 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
"directory_completion/".to_string(),
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
|
"test_a_symlink".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
@ -1759,3 +1901,31 @@ fn alias_offset_bug_7754() {
|
|||||||
// This crashes before PR #7756
|
// This crashes before PR #7756
|
||||||
let _suggestions = completer.complete("ll -a | c", 9);
|
let _suggestions = completer.complete("ll -a | c", 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn nested_block(mut completer: NuCompleter) {
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
|
||||||
|
let suggestions = completer.complete("somecmd | lines | each { tst - }", 30);
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("somecmd | lines | each { tst -}", 30);
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn incomplete_nested_block(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("somecmd | lines | each { tst -", 30);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn deeply_nested_block(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete(
|
||||||
|
"somecmd | lines | each { print ([each (print) (tst -)]) }",
|
||||||
|
52,
|
||||||
|
);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(&expected, &suggestions);
|
||||||
|
}
|
||||||
|
@ -98,14 +98,14 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"NU_LIB_DIRS".to_string(),
|
"NU_LIB_DIRS".to_string(),
|
||||||
Value::List {
|
Value::list(
|
||||||
vals: vec![
|
vec![
|
||||||
Value::string(file(dir.join("lib-dir1")), dir_span),
|
Value::string(file(dir.join("lib-dir1")), dir_span),
|
||||||
Value::string(file(dir.join("lib-dir2")), dir_span),
|
Value::string(file(dir.join("lib-dir2")), dir_span),
|
||||||
Value::string(file(dir.join("lib-dir3")), dir_span),
|
Value::string(file(dir.join("lib-dir3")), dir_span),
|
||||||
],
|
],
|
||||||
internal_span: dir_span,
|
dir_span,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ version = "0.101.0"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.102.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.102.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.102.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
|
||||||
nu-json = { version = "0.101.0", path = "../nu-json" }
|
nu-json = { version = "0.102.0", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.102.0" }
|
||||||
nu-pretty-hex = { version = "0.101.0", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.102.0", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.102.0", default-features = false }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
@ -34,8 +34,9 @@ serde = { workspace = true }
|
|||||||
serde_urlencoded = { workspace = true }
|
serde_urlencoded = { workspace = true }
|
||||||
v_htmlescape = { workspace = true }
|
v_htmlescape = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
mime = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.102.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.101.0" }
|
nu-command = { path = "../nu-command", version = "0.102.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.102.0" }
|
||||||
|
@ -43,7 +43,12 @@ mod test_examples {
|
|||||||
signature.operates_on_cell_paths(),
|
signature.operates_on_cell_paths(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
|
check_example_evaluates_to_expected_output(
|
||||||
|
cmd.name(),
|
||||||
|
&example,
|
||||||
|
cwd.as_path(),
|
||||||
|
&mut engine_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_signature_input_output_types_entries_have_examples(
|
check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
use std::io::{self, Read, Write};
|
|
||||||
|
|
||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_protocol::Signals;
|
use nu_protocol::{report_parse_warning, ParseWarning};
|
||||||
use num_traits::ToPrimitive;
|
|
||||||
|
|
||||||
pub struct Arguments {
|
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CmdArgument for Arguments {
|
|
||||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.cell_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsInto;
|
pub struct BitsInto;
|
||||||
@ -42,15 +28,15 @@ impl Command for BitsInto {
|
|||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"for a data structure input, convert data at the given cell paths",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Deprecated)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Convert value to a binary primitive."
|
"Convert value to a binary string."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "cast"]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -60,7 +46,17 @@ impl Command for BitsInto {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
into_bits(engine_state, stack, call, input)
|
let head = call.head;
|
||||||
|
report_parse_warning(
|
||||||
|
&StateWorkingSet::new(engine_state),
|
||||||
|
&ParseWarning::DeprecatedWarning {
|
||||||
|
old_command: "into bits".into(),
|
||||||
|
new_suggestion: "use `format bits`".into(),
|
||||||
|
span: head,
|
||||||
|
url: "`help format bits`".into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
crate::extra::strings::format::format_bits(engine_state, stack, call, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -111,126 +107,6 @@ impl Command for BitsInto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_bits(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
|
||||||
|
|
||||||
if let PipelineData::ByteStream(stream, metadata) = input {
|
|
||||||
Ok(PipelineData::ByteStream(
|
|
||||||
byte_stream_to_bits(stream, head),
|
|
||||||
metadata,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let args = Arguments { cell_paths };
|
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
|
|
||||||
if let Some(mut reader) = stream.reader() {
|
|
||||||
let mut is_first = true;
|
|
||||||
ByteStream::from_fn(
|
|
||||||
head,
|
|
||||||
Signals::empty(),
|
|
||||||
ByteStreamType::String,
|
|
||||||
move |buffer| {
|
|
||||||
let mut byte = [0];
|
|
||||||
if reader.read(&mut byte[..]).err_span(head)? > 0 {
|
|
||||||
// Format the byte as bits
|
|
||||||
if is_first {
|
|
||||||
is_first = false;
|
|
||||||
} else {
|
|
||||||
buffer.push(b' ');
|
|
||||||
}
|
|
||||||
write!(buffer, "{:08b}", byte[0]).expect("format failed");
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
// EOF
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
|
||||||
if let Some(v) = num.to_i8() {
|
|
||||||
let bytes = v.to_ne_bytes();
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in bytes {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
} else if let Some(v) = num.to_i16() {
|
|
||||||
let bytes = v.to_ne_bytes();
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in bytes {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
} else if let Some(v) = num.to_i32() {
|
|
||||||
let bytes = v.to_ne_bytes();
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in bytes {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
} else {
|
|
||||||
let bytes = num.to_ne_bytes();
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in bytes {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
|
||||||
match input {
|
|
||||||
Value::Binary { val, .. } => {
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in val {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
}
|
|
||||||
Value::Int { val, .. } => convert_to_smallest_number_type(*val, span),
|
|
||||||
Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span),
|
|
||||||
Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span),
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
let raw_bytes = val.as_bytes();
|
|
||||||
let mut raw_string = "".to_string();
|
|
||||||
for ch in raw_bytes {
|
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
|
||||||
}
|
|
||||||
Value::string(raw_string.trim(), span)
|
|
||||||
}
|
|
||||||
Value::Bool { val, .. } => {
|
|
||||||
let v = <i64 as From<bool>>::from(*val);
|
|
||||||
convert_to_smallest_number_type(v, span)
|
|
||||||
}
|
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
|
||||||
Value::Error { .. } => input.clone(),
|
|
||||||
other => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "int, filesize, string, duration, binary, or bool".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: span,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::{report_parse_warning, ParseWarning};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Fmt;
|
pub struct Fmt;
|
||||||
@ -16,11 +16,11 @@ impl Command for Fmt {
|
|||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("fmt")
|
Signature::build("fmt")
|
||||||
.input_output_types(vec![(Type::Number, Type::record())])
|
.input_output_types(vec![(Type::Number, Type::record())])
|
||||||
.category(Category::Conversions)
|
.category(Category::Deprecated)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["display", "render", "format"]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -47,72 +47,20 @@ impl Command for Fmt {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
fmt(engine_state, stack, call, input)
|
let head = call.head;
|
||||||
}
|
report_parse_warning(
|
||||||
}
|
&StateWorkingSet::new(engine_state),
|
||||||
|
&ParseWarning::DeprecatedWarning {
|
||||||
fn fmt(
|
old_command: "fmt".into(),
|
||||||
engine_state: &EngineState,
|
new_suggestion: "use `format number`".into(),
|
||||||
stack: &mut Stack,
|
span: head,
|
||||||
call: &Call,
|
url: "`help format number`".into(),
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let args = CellPathOnlyArgs::from(cell_paths);
|
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|
||||||
match input {
|
|
||||||
Value::Float { val, .. } => fmt_it_64(*val, span),
|
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
|
||||||
Value::Filesize { val, .. } => fmt_it(val.get(), span),
|
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
|
||||||
Value::Error { .. } => input.clone(),
|
|
||||||
other => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "float, int, or filesize".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: span,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
},
|
||||||
span,
|
);
|
||||||
),
|
crate::extra::strings::format::format_number(engine_state, stack, call, input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_it(num: i64, span: Span) -> Value {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"binary" => Value::string(format!("{num:#b}"), span),
|
|
||||||
"debug" => Value::string(format!("{num:#?}"), span),
|
|
||||||
"display" => Value::string(format!("{num}"), span),
|
|
||||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
|
||||||
"lowerhex" => Value::string(format!("{num:#x}"), span),
|
|
||||||
"octal" => Value::string(format!("{num:#o}"), span),
|
|
||||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
|
||||||
"upperhex" => Value::string(format!("{num:#X}"), span),
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_it_64(num: f64, span: Span) -> Value {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
|
||||||
"debug" => Value::string(format!("{num:#?}"), span),
|
|
||||||
"display" => Value::string(format!("{num}"), span),
|
|
||||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
|
||||||
"lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
|
|
||||||
"octal" => Value::string(format!("{:0o}", num.to_bits()), span),
|
|
||||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
|
||||||
"upperhex" => Value::string(format!("{:0X}", num.to_bits()), span),
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -13,6 +13,8 @@ impl Command for Rotate {
|
|||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::record(), Type::table()),
|
(Type::record(), Type::table()),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
|
(Type::list(Type::Any), Type::table()),
|
||||||
|
(Type::String, Type::table()),
|
||||||
])
|
])
|
||||||
.switch("ccw", "rotate counter clockwise", None)
|
.switch("ccw", "rotate counter clockwise", None)
|
||||||
.rest(
|
.rest(
|
||||||
@ -21,6 +23,7 @@ impl Command for Rotate {
|
|||||||
"the names to give columns once rotated",
|
"the names to give columns once rotated",
|
||||||
)
|
)
|
||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
|
@ -330,7 +330,12 @@ fn to_html(
|
|||||||
output_string = run_regexes(®ex_hm, &output_string);
|
output_string = run_regexes(®ex_hm, &output_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::string(output_string, head).into_pipeline_data())
|
let metadata = PipelineMetadata {
|
||||||
|
data_source: nu_protocol::DataSource::None,
|
||||||
|
content_type: Some(mime::TEXT_HTML_UTF_8.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::string(output_string, head).into_pipeline_data_with_metadata(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_demo(span: Span) -> PipelineData {
|
fn theme_demo(span: Span) -> PipelineData {
|
||||||
|
@ -46,6 +46,8 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
bind_command!(
|
bind_command!(
|
||||||
strings::format::FormatPattern,
|
strings::format::FormatPattern,
|
||||||
|
strings::format::FormatBits,
|
||||||
|
strings::format::FormatNumber,
|
||||||
strings::str_::case::Str,
|
strings::str_::case::Str,
|
||||||
strings::str_::case::StrCamelCase,
|
strings::str_::case::StrCamelCase,
|
||||||
strings::str_::case::StrKebabCase,
|
strings::str_::case::StrKebabCase,
|
||||||
|
249
crates/nu-cmd-extra/src/extra/strings/format/bits.rs
Normal file
249
crates/nu-cmd-extra/src/extra/strings/format/bits.rs
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
use nu_protocol::{shell_error::io::IoError, Signals};
|
||||||
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FormatBits;
|
||||||
|
|
||||||
|
impl Command for FormatBits {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"format bits"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("format bits")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::String),
|
||||||
|
(Type::Int, Type::String),
|
||||||
|
(Type::Filesize, Type::String),
|
||||||
|
(Type::Duration, Type::String),
|
||||||
|
(Type::String, Type::String),
|
||||||
|
(Type::Bool, Type::String),
|
||||||
|
(Type::table(), Type::table()),
|
||||||
|
(Type::record(), Type::record()),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, convert data at the given cell paths",
|
||||||
|
)
|
||||||
|
.category(Category::Conversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Convert value to a string of binary data represented by 0 and 1."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["convert", "cast", "binary"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
format_bits(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "convert a binary value into a string, padded to 8 places with 0s",
|
||||||
|
example: "0x[1] | format bits",
|
||||||
|
result: Some(Value::string("00000001",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert an int into a string, padded to 8 places with 0s",
|
||||||
|
example: "1 | format bits",
|
||||||
|
result: Some(Value::string("00000001",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert a filesize value into a string, padded to 8 places with 0s",
|
||||||
|
example: "1b | format bits",
|
||||||
|
result: Some(Value::string("00000001",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert a duration value into a string, padded to 8 places with 0s",
|
||||||
|
example: "1ns | format bits",
|
||||||
|
result: Some(Value::string("00000001",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert a boolean value into a string, padded to 8 places with 0s",
|
||||||
|
example: "true | format bits",
|
||||||
|
result: Some(Value::string("00000001",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert a string into a raw binary string, padded with 0s to 8 places",
|
||||||
|
example: "'nushell.sh' | format bits",
|
||||||
|
result: Some(Value::string("01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000",
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: crate public only during deprecation
|
||||||
|
pub(crate) fn format_bits(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||||
|
Ok(PipelineData::ByteStream(
|
||||||
|
byte_stream_to_bits(stream, head),
|
||||||
|
metadata,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let args = Arguments { cell_paths };
|
||||||
|
operate(action, args, input, call.head, engine_state.signals())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
|
||||||
|
if let Some(mut reader) = stream.reader() {
|
||||||
|
let mut is_first = true;
|
||||||
|
ByteStream::from_fn(
|
||||||
|
head,
|
||||||
|
Signals::empty(),
|
||||||
|
ByteStreamType::String,
|
||||||
|
move |buffer| {
|
||||||
|
let mut byte = [0];
|
||||||
|
if reader
|
||||||
|
.read(&mut byte[..])
|
||||||
|
.map_err(|err| IoError::new(err.kind(), head, None))?
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
// Format the byte as bits
|
||||||
|
if is_first {
|
||||||
|
is_first = false;
|
||||||
|
} else {
|
||||||
|
buffer.push(b' ');
|
||||||
|
}
|
||||||
|
write!(buffer, "{:08b}", byte[0]).expect("format failed");
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// EOF
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
||||||
|
if let Some(v) = num.to_i8() {
|
||||||
|
let bytes = v.to_ne_bytes();
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in bytes {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
} else if let Some(v) = num.to_i16() {
|
||||||
|
let bytes = v.to_ne_bytes();
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in bytes {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
} else if let Some(v) = num.to_i32() {
|
||||||
|
let bytes = v.to_ne_bytes();
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in bytes {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
} else {
|
||||||
|
let bytes = num.to_ne_bytes();
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in bytes {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||||
|
match input {
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in val {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
}
|
||||||
|
Value::Int { val, .. } => convert_to_smallest_number_type(*val, span),
|
||||||
|
Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span),
|
||||||
|
Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span),
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
let raw_bytes = val.as_bytes();
|
||||||
|
let mut raw_string = "".to_string();
|
||||||
|
for ch in raw_bytes {
|
||||||
|
raw_string.push_str(&format!("{:08b} ", ch));
|
||||||
|
}
|
||||||
|
Value::string(raw_string.trim(), span)
|
||||||
|
}
|
||||||
|
Value::Bool { val, .. } => {
|
||||||
|
let v = <i64 as From<bool>>::from(*val);
|
||||||
|
convert_to_smallest_number_type(v, span)
|
||||||
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
|
other => Value::error(
|
||||||
|
ShellError::OnlySupportsThisInputType {
|
||||||
|
exp_input_type: "int, filesize, string, duration, binary, or bool".into(),
|
||||||
|
wrong_type: other.get_type().to_string(),
|
||||||
|
dst_span: span,
|
||||||
|
src_span: other.span(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(FormatBits {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,9 @@
|
|||||||
|
mod bits;
|
||||||
mod command;
|
mod command;
|
||||||
|
mod number;
|
||||||
|
|
||||||
pub(crate) use command::FormatPattern;
|
pub(crate) use command::FormatPattern;
|
||||||
|
// TODO remove `format_bits` visibility after removal of into bits
|
||||||
|
pub(crate) use bits::{format_bits, FormatBits};
|
||||||
|
// TODO remove `format_number` visibility after removal of into bits
|
||||||
|
pub(crate) use number::{format_number, FormatNumber};
|
||||||
|
126
crates/nu-cmd-extra/src/extra/strings/format/number.rs
Normal file
126
crates/nu-cmd-extra/src/extra/strings/format/number.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FormatNumber;
|
||||||
|
|
||||||
|
impl Command for FormatNumber {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"format number"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Format a number."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("format number")
|
||||||
|
.input_output_types(vec![(Type::Number, Type::record())])
|
||||||
|
.category(Category::Conversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["display", "render", "format"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Get a record containing multiple formats for the number 42",
|
||||||
|
example: "42 | format number",
|
||||||
|
result: Some(Value::test_record(record! {
|
||||||
|
"binary" => Value::test_string("0b101010"),
|
||||||
|
"debug" => Value::test_string("42"),
|
||||||
|
"display" => Value::test_string("42"),
|
||||||
|
"lowerexp" => Value::test_string("4.2e1"),
|
||||||
|
"lowerhex" => Value::test_string("0x2a"),
|
||||||
|
"octal" => Value::test_string("0o52"),
|
||||||
|
"upperexp" => Value::test_string("4.2E1"),
|
||||||
|
"upperhex" => Value::test_string("0x2A"),
|
||||||
|
})),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
format_number(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format_number(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.signals())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
|
match input {
|
||||||
|
Value::Float { val, .. } => format_f64(*val, span),
|
||||||
|
Value::Int { val, .. } => format_i64(*val, span),
|
||||||
|
Value::Filesize { val, .. } => format_i64(val.get(), span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
|
other => Value::error(
|
||||||
|
ShellError::OnlySupportsThisInputType {
|
||||||
|
exp_input_type: "float, int, or filesize".into(),
|
||||||
|
wrong_type: other.get_type().to_string(),
|
||||||
|
dst_span: span,
|
||||||
|
src_span: other.span(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_i64(num: i64, span: Span) -> Value {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"binary" => Value::string(format!("{num:#b}"), span),
|
||||||
|
"debug" => Value::string(format!("{num:#?}"), span),
|
||||||
|
"display" => Value::string(format!("{num}"), span),
|
||||||
|
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||||
|
"lowerhex" => Value::string(format!("{num:#x}"), span),
|
||||||
|
"octal" => Value::string(format!("{num:#o}"), span),
|
||||||
|
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||||
|
"upperhex" => Value::string(format!("{num:#X}"), span),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_f64(num: f64, span: Span) -> Value {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
||||||
|
"debug" => Value::string(format!("{num:#?}"), span),
|
||||||
|
"display" => Value::string(format!("{num}"), span),
|
||||||
|
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||||
|
"lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
|
||||||
|
"octal" => Value::string(format!("{:0o}", num.to_bits()), span),
|
||||||
|
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||||
|
"upperhex" => Value::string(format!("{:0X}", num.to_bits()), span),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(FormatNumber {})
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,12 @@ use nu_test_support::nu;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn byte_stream_into_bits() {
|
fn byte_stream_into_bits() {
|
||||||
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits");
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits");
|
||||||
assert_eq!("00000001 00000010 00000011", result.out);
|
assert_eq!("00000001 00000010 00000011", result.out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn byte_stream_into_bits_is_stream() {
|
fn byte_stream_into_bits_is_stream() {
|
||||||
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe");
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits | describe");
|
||||||
assert_eq!("string (stream)", result.out);
|
assert_eq!("string (stream)", result.out);
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
mod into;
|
mod format;
|
||||||
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -15,16 +15,16 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.102.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.102.0", default-features = false }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "0.37", default-features = false }
|
shadow-rs = { version = "0.38", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.37", default-features = false }
|
shadow-rs = { version = "0.38", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["os"]
|
default = ["os"]
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
|
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
|
||||||
#[cfg(feature = "os")]
|
#[cfg(feature = "os")]
|
||||||
use nu_protocol::process::{ChildPipe, ChildProcess};
|
use nu_protocol::process::{ChildPipe, ChildProcess};
|
||||||
use nu_protocol::{engine::Closure, ByteStream, ByteStreamSource, OutDest};
|
use nu_protocol::{
|
||||||
|
engine::Closure, shell_error::io::IoError, ByteStream, ByteStreamSource, OutDest,
|
||||||
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read},
|
||||||
@ -143,10 +145,16 @@ impl Command for Do {
|
|||||||
.name("stdout consumer".to_string())
|
.name("stdout consumer".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
stdout.read_to_end(&mut buf)?;
|
stdout.read_to_end(&mut buf).map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not read stdout to end",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Ok::<_, ShellError>(buf)
|
Ok::<_, ShellError>(buf)
|
||||||
})
|
})
|
||||||
.err_span(head)
|
.map_err(|err| IoError::new(err.kind(), head, None))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
@ -156,7 +164,9 @@ impl Command for Do {
|
|||||||
None => String::new(),
|
None => String::new(),
|
||||||
Some(mut stderr) => {
|
Some(mut stderr) => {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
stderr.read_to_string(&mut buf).err_span(span)?;
|
stderr
|
||||||
|
.read_to_string(&mut buf)
|
||||||
|
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -294,15 +304,7 @@ fn bind_args_to(
|
|||||||
.expect("internal error: all custom parameters must have var_ids");
|
.expect("internal error: all custom parameters must have var_ids");
|
||||||
if let Some(result) = val_iter.next() {
|
if let Some(result) = val_iter.next() {
|
||||||
let param_type = param.shape.to_type();
|
let param_type = param.shape.to_type();
|
||||||
if required && !result.get_type().is_subtype(¶m_type) {
|
if required && !result.is_subtype_of(¶m_type) {
|
||||||
// need to check if result is an empty list, and param_type is table or list
|
|
||||||
// nushell needs to pass type checking for the case.
|
|
||||||
let empty_list_matches = result
|
|
||||||
.as_list()
|
|
||||||
.map(|l| l.is_empty() && matches!(param_type, Type::List(_) | Type::Table(_)))
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if !empty_list_matches {
|
|
||||||
return Err(ShellError::CantConvert {
|
return Err(ShellError::CantConvert {
|
||||||
to_type: param.shape.to_type().to_string(),
|
to_type: param.shape.to_type().to_string(),
|
||||||
from_type: result.get_type().to_string(),
|
from_type: result.get_type().to_string(),
|
||||||
@ -310,7 +312,6 @@ fn bind_args_to(
|
|||||||
help: None,
|
help: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
stack.add_var(var_id, result);
|
stack.add_var(var_id, result);
|
||||||
} else if let Some(value) = ¶m.default_value {
|
} else if let Some(value) = ¶m.default_value {
|
||||||
stack.add_var(var_id, value.to_owned())
|
stack.add_var(var_id, value.to_owned())
|
||||||
|
@ -24,8 +24,8 @@ impl Command for OverlayUse {
|
|||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required(
|
.required(
|
||||||
"name",
|
"name",
|
||||||
SyntaxShape::String,
|
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
|
||||||
"Module name to use overlay for.",
|
"Module name to use overlay for (`null` for no-op).",
|
||||||
)
|
)
|
||||||
.optional(
|
.optional(
|
||||||
"as",
|
"as",
|
||||||
@ -61,6 +61,11 @@ impl Command for OverlayUse {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let noop = call.get_parser_info(caller_stack, "noop");
|
||||||
|
if noop.is_some() {
|
||||||
|
return Ok(PipelineData::empty());
|
||||||
|
}
|
||||||
|
|
||||||
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||||
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||||
|
|
||||||
|
@ -22,7 +22,11 @@ impl Command for Use {
|
|||||||
Signature::build("use")
|
Signature::build("use")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required("module", SyntaxShape::String, "Module or module file.")
|
.required(
|
||||||
|
"module",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
|
||||||
|
"Module or module file (`null` for no-op).",
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"members",
|
"members",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
@ -54,6 +58,9 @@ This command is a parser keyword. For details, check:
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if call.get_parser_info(caller_stack, "noop").is_some() {
|
||||||
|
return Ok(PipelineData::empty());
|
||||||
|
}
|
||||||
let Some(Expression {
|
let Some(Expression {
|
||||||
expr: Expr::ImportPattern(import_pattern),
|
expr: Expr::ImportPattern(import_pattern),
|
||||||
..
|
..
|
||||||
|
@ -19,18 +19,15 @@ pub fn check_example_input_and_output_types_match_command_signature(
|
|||||||
|
|
||||||
// Skip tests that don't have results to compare to
|
// Skip tests that don't have results to compare to
|
||||||
if let Some(example_output) = example.result.as_ref() {
|
if let Some(example_output) = example.result.as_ref() {
|
||||||
if let Some(example_input_type) =
|
if let Some(example_input) =
|
||||||
eval_pipeline_without_terminal_expression(example.example, cwd, engine_state)
|
eval_pipeline_without_terminal_expression(example.example, cwd, engine_state)
|
||||||
{
|
{
|
||||||
let example_input_type = example_input_type.get_type();
|
|
||||||
let example_output_type = example_output.get_type();
|
|
||||||
|
|
||||||
let example_matches_signature =
|
let example_matches_signature =
|
||||||
signature_input_output_types
|
signature_input_output_types
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(sig_in_type, sig_out_type)| {
|
.any(|(sig_in_type, sig_out_type)| {
|
||||||
example_input_type.is_subtype(sig_in_type)
|
example_input.is_subtype_of(sig_in_type)
|
||||||
&& example_output_type.is_subtype(sig_out_type)
|
&& example_output.is_subtype_of(sig_out_type)
|
||||||
&& {
|
&& {
|
||||||
witnessed_type_transformations
|
witnessed_type_transformations
|
||||||
.insert((sig_in_type.clone(), sig_out_type.clone()));
|
.insert((sig_in_type.clone(), sig_out_type.clone()));
|
||||||
@ -38,6 +35,9 @@ pub fn check_example_input_and_output_types_match_command_signature(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let example_input_type = example_input.get_type();
|
||||||
|
let example_output_type = example_output.get_type();
|
||||||
|
|
||||||
// The example type checks as a cell path operation if both:
|
// The example type checks as a cell path operation if both:
|
||||||
// 1. The command is declared to operate on cell paths.
|
// 1. The command is declared to operate on cell paths.
|
||||||
// 2. The example_input_type is list or record or table, and the example
|
// 2. The example_input_type is list or record or table, and the example
|
||||||
@ -139,6 +139,7 @@ pub fn eval_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_example_evaluates_to_expected_output(
|
pub fn check_example_evaluates_to_expected_output(
|
||||||
|
cmd_name: &str,
|
||||||
example: &Example,
|
example: &Example,
|
||||||
cwd: &std::path::Path,
|
cwd: &std::path::Path,
|
||||||
engine_state: &mut Box<EngineState>,
|
engine_state: &mut Box<EngineState>,
|
||||||
@ -159,11 +160,17 @@ pub fn check_example_evaluates_to_expected_output(
|
|||||||
// If the command you are testing requires to compare another case, then
|
// If the command you are testing requires to compare another case, then
|
||||||
// you need to define its equality in the Value struct
|
// you need to define its equality in the Value struct
|
||||||
if let Some(expected) = example.result.as_ref() {
|
if let Some(expected) = example.result.as_ref() {
|
||||||
|
let expected = DebuggableValue(expected);
|
||||||
|
let result = DebuggableValue(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DebuggableValue(&result),
|
result,
|
||||||
DebuggableValue(expected),
|
expected,
|
||||||
"The example result differs from the expected value",
|
"Error: The result of example '{}' for the command '{}' differs from the expected value.\n\nExpected: {:?}\nActual: {:?}\n",
|
||||||
)
|
example.description,
|
||||||
|
cmd_name,
|
||||||
|
expected,
|
||||||
|
result,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,12 @@ mod test_examples {
|
|||||||
signature.operates_on_cell_paths(),
|
signature.operates_on_cell_paths(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
|
check_example_evaluates_to_expected_output(
|
||||||
|
cmd.name(),
|
||||||
|
&example,
|
||||||
|
cwd.as_path(),
|
||||||
|
&mut engine_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_signature_input_output_types_entries_have_examples(
|
check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-plugin"
|
name = "nu-cmd-plugin"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ version = "0.101.0"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0" }
|
nu-engine = { path = "../nu-engine", version = "0.102.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.102.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", features = ["plugin"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", features = ["plugin"] }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.101.0" }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.102.0" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use crate::util::{get_plugin_dirs, modify_plugin_file};
|
use crate::util::{get_plugin_dirs, modify_plugin_file};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
|
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
|
||||||
use nu_protocol::{PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin};
|
use nu_protocol::{
|
||||||
use std::sync::Arc;
|
shell_error::io::IoError, PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin,
|
||||||
|
};
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PluginAdd;
|
pub struct PluginAdd;
|
||||||
@ -86,11 +88,14 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
|||||||
let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
|
let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
|
||||||
get_plugin_dirs(engine_state, stack)
|
get_plugin_dirs(engine_state, stack)
|
||||||
})
|
})
|
||||||
.err_span(filename.span)?;
|
.map_err(|err| IoError::new(err.kind(), filename.span, PathBuf::from(filename.item)))?;
|
||||||
|
|
||||||
let shell_expanded = shell
|
let shell_expanded = shell
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| nu_path::canonicalize_with(&s.item, &cwd).err_span(s.span))
|
.map(|s| {
|
||||||
|
nu_path::canonicalize_with(&s.item, &cwd)
|
||||||
|
.map_err(|err| IoError::new(err.kind(), s.span, None))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
// Parse the plugin filename so it can be used to spawn the plugin
|
// Parse the plugin filename so it can be used to spawn the plugin
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{command_prelude::*, current_dir};
|
use nu_engine::{command_prelude::*, current_dir};
|
||||||
use nu_protocol::{engine::StateWorkingSet, PluginRegistryFile};
|
use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError, PluginRegistryFile};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -45,21 +45,16 @@ pub(crate) fn read_plugin_file(
|
|||||||
// Try to read the plugin file if it exists
|
// Try to read the plugin file if it exists
|
||||||
if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
||||||
PluginRegistryFile::read_from(
|
PluginRegistryFile::read_from(
|
||||||
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
File::open(&plugin_registry_file_path)
|
||||||
msg: format!(
|
.map_err(|err| IoError::new(err.kind(), file_span, plugin_registry_file_path))?,
|
||||||
"failed to read `{}`: {}",
|
|
||||||
plugin_registry_file_path.display(),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
span: file_span,
|
|
||||||
})?,
|
|
||||||
Some(file_span),
|
Some(file_span),
|
||||||
)
|
)
|
||||||
} else if let Some(path) = custom_path {
|
} else if let Some(path) = custom_path {
|
||||||
Err(ShellError::FileNotFound {
|
Err(ShellError::Io(IoError::new(
|
||||||
file: path.item.clone(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: path.span,
|
path.span,
|
||||||
})
|
PathBuf::from(&path.item),
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(PluginRegistryFile::default())
|
Ok(PluginRegistryFile::default())
|
||||||
}
|
}
|
||||||
@ -80,13 +75,8 @@ pub(crate) fn modify_plugin_file(
|
|||||||
// Try to read the plugin file if it exists
|
// Try to read the plugin file if it exists
|
||||||
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
||||||
PluginRegistryFile::read_from(
|
PluginRegistryFile::read_from(
|
||||||
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
File::open(&plugin_registry_file_path).map_err(|err| {
|
||||||
msg: format!(
|
IoError::new(err.kind(), file_span, plugin_registry_file_path.clone())
|
||||||
"failed to read `{}`: {}",
|
|
||||||
plugin_registry_file_path.display(),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
span: file_span,
|
|
||||||
})?,
|
})?,
|
||||||
Some(file_span),
|
Some(file_span),
|
||||||
)?
|
)?
|
||||||
@ -99,14 +89,8 @@ pub(crate) fn modify_plugin_file(
|
|||||||
|
|
||||||
// Save the modified file on success
|
// Save the modified file on success
|
||||||
contents.write_to(
|
contents.write_to(
|
||||||
File::create(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
File::create(&plugin_registry_file_path)
|
||||||
msg: format!(
|
.map_err(|err| IoError::new(err.kind(), file_span, plugin_registry_file_path))?,
|
||||||
"failed to create `{}`: {}",
|
|
||||||
plugin_registry_file_path.display(),
|
|
||||||
err
|
|
||||||
),
|
|
||||||
span: file_span,
|
|
||||||
})?,
|
|
||||||
Some(span),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -14,12 +14,12 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
|
||||||
nu-json = { path = "../nu-json", version = "0.101.0" }
|
nu-json = { path = "../nu-json", version = "0.102.0" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.102.0" }
|
@ -169,7 +169,7 @@ impl<'a> StyleComputer<'a> {
|
|||||||
|
|
||||||
// Because EngineState doesn't have Debug (Dec 2022),
|
// Because EngineState doesn't have Debug (Dec 2022),
|
||||||
// this incomplete representation must be used.
|
// this incomplete representation must be used.
|
||||||
impl<'a> Debug for StyleComputer<'a> {
|
impl Debug for StyleComputer<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
f.debug_struct("StyleComputer")
|
f.debug_struct("StyleComputer")
|
||||||
.field("map", &self.map)
|
.field("map", &self.map)
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.101.0"
|
version = "0.102.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -16,21 +16,21 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.102.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.101.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.102.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.101.0" }
|
nu-glob = { path = "../nu-glob", version = "0.102.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.101.0" }
|
nu-json = { path = "../nu-json", version = "0.102.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.102.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.102.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.101.0" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.102.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
|
||||||
nu-system = { path = "../nu-system", version = "0.101.0" }
|
nu-system = { path = "../nu-system", version = "0.102.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.101.0" }
|
nu-table = { path = "../nu-table", version = "0.102.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.101.0" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.102.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.102.0", default-features = false }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
nuon = { path = "../nuon", version = "0.101.0" }
|
nuon = { path = "../nuon", version = "0.102.0" }
|
||||||
|
|
||||||
alphanumeric-sort = { workspace = true }
|
alphanumeric-sort = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
@ -45,6 +45,7 @@ chrono-humanize = { workspace = true }
|
|||||||
chrono-tz = { workspace = true }
|
chrono-tz = { workspace = true }
|
||||||
crossterm = { workspace = true, optional = true }
|
crossterm = { workspace = true, optional = true }
|
||||||
csv = { workspace = true }
|
csv = { workspace = true }
|
||||||
|
devicons = { workspace = true }
|
||||||
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
|
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
|
||||||
digest = { workspace = true, default-features = false }
|
digest = { workspace = true, default-features = false }
|
||||||
dtparse = { workspace = true }
|
dtparse = { workspace = true }
|
||||||
@ -76,7 +77,6 @@ quick-xml = { workspace = true }
|
|||||||
rand = { workspace = true, optional = true }
|
rand = { workspace = true, optional = true }
|
||||||
getrandom = { workspace = true, optional = true }
|
getrandom = { workspace = true, optional = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
regex = { workspace = true }
|
|
||||||
roxmltree = { workspace = true }
|
roxmltree = { workspace = true }
|
||||||
rusqlite = { workspace = true, features = ["bundled", "backup", "chrono"], optional = true }
|
rusqlite = { workspace = true, features = ["bundled", "backup", "chrono"], optional = true }
|
||||||
rmp = { workspace = true }
|
rmp = { workspace = true }
|
||||||
@ -91,7 +91,8 @@ tabled = { workspace = true, features = ["ansi"], default-features = false }
|
|||||||
titlecase = { workspace = true }
|
titlecase = { workspace = true }
|
||||||
toml = { workspace = true, features = ["preserve_order"] }
|
toml = { workspace = true, features = ["preserve_order"] }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json"] }
|
update-informer = { workspace = true, optional = true }
|
||||||
|
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"], optional = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
uu_cp = { workspace = true, optional = true }
|
uu_cp = { workspace = true, optional = true }
|
||||||
uu_mkdir = { workspace = true, optional = true }
|
uu_mkdir = { workspace = true, optional = true }
|
||||||
@ -105,7 +106,8 @@ v_htmlescape = { workspace = true }
|
|||||||
wax = { workspace = true }
|
wax = { workspace = true }
|
||||||
which = { workspace = true, optional = true }
|
which = { workspace = true, optional = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
data-encoding = { version = "2.6.0", features = ["alloc"] }
|
data-encoding = { version = "2.7.0", features = ["alloc"] }
|
||||||
|
web-time = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = { workspace = true }
|
winreg = { workspace = true }
|
||||||
@ -175,7 +177,8 @@ js = [
|
|||||||
network = [
|
network = [
|
||||||
"multipart-rs",
|
"multipart-rs",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"ureq/native-tls",
|
"update-informer/native-tls",
|
||||||
|
"ureq",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -187,14 +190,15 @@ sqlite = ["rusqlite"]
|
|||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.102.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.102.0" }
|
||||||
|
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
mockito = { workspace = true, default-features = false }
|
mockito = { workspace = true, default-features = false }
|
||||||
quickcheck = { workspace = true }
|
quickcheck = { workspace = true }
|
||||||
quickcheck_macros = { workspace = true }
|
quickcheck_macros = { workspace = true }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
|
rstest_reuse = { workspace = true }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
rand_chacha = { workspace = true }
|
rand_chacha = { workspace = true }
|
@ -1,15 +1,14 @@
|
|||||||
use nu_cmd_base::{
|
use std::ops::Bound;
|
||||||
input_handler::{operate, CmdArgument},
|
|
||||||
util,
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
};
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::Range;
|
use nu_protocol::{IntRange, Range};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BytesAt;
|
pub struct BytesAt;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
indexes: Subbytes,
|
range: IntRange,
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,15 +18,6 @@ impl CmdArgument for Arguments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(isize, isize)> for Subbytes {
|
|
||||||
fn from(input: (isize, isize)) -> Self {
|
|
||||||
Self(input.0, input.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct Subbytes(isize, isize);
|
|
||||||
|
|
||||||
impl Command for BytesAt {
|
impl Command for BytesAt {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"bytes at"
|
"bytes at"
|
||||||
@ -44,6 +34,7 @@ impl Command for BytesAt {
|
|||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
])
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("range", SyntaxShape::Range, "The range to get bytes.")
|
.required("range", SyntaxShape::Range, "The range to get bytes.")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
@ -68,51 +59,66 @@ impl Command for BytesAt {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let range: Range = call.req(engine_state, stack, 0)?;
|
let range = match call.req(engine_state, stack, 0)? {
|
||||||
let indexes = match util::process_range(&range) {
|
Range::IntRange(range) => range,
|
||||||
Ok(idxs) => idxs.into(),
|
_ => {
|
||||||
Err(processing_error) => {
|
return Err(ShellError::UnsupportedInput {
|
||||||
return Err(processing_error("could not perform subbytes", call.head));
|
msg: "Float ranges are not supported for byte streams".into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: call.head,
|
||||||
|
input_span: call.head,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let args = Arguments {
|
|
||||||
indexes,
|
|
||||||
cell_paths,
|
|
||||||
};
|
|
||||||
|
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||||
|
let stream = stream.slice(call.head, call.arguments_span(), range)?;
|
||||||
|
Ok(PipelineData::ByteStream(stream, metadata))
|
||||||
|
} else {
|
||||||
|
operate(
|
||||||
|
map_value,
|
||||||
|
Arguments { range, cell_paths },
|
||||||
|
input,
|
||||||
|
call.head,
|
||||||
|
engine_state.signals(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
|
description: "Extract bytes starting from a specific index",
|
||||||
example: " 0x[33 44 55 10 01 13] | bytes at 3..<4",
|
example: "{ data: 0x[33 44 55 10 01 13 10] } | bytes at 3.. data",
|
||||||
result: Some(Value::test_binary(vec![0x10])),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Get a subbytes `0x[10 01 13]` from the bytes `0x[33 44 55 10 01 13]`",
|
|
||||||
example: " 0x[33 44 55 10 01 13] | bytes at 3..6",
|
|
||||||
result: Some(Value::test_binary(vec![0x10, 0x01, 0x13])),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Get the remaining characters from a starting index",
|
|
||||||
example: " { data: 0x[33 44 55 10 01 13] } | bytes at 3.. data",
|
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_record(record! {
|
||||||
"data" => Value::test_binary(vec![0x10, 0x01, 0x13]),
|
"data" => Value::test_binary(vec![0x10, 0x01, 0x13, 0x10]),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the characters from the beginning until ending index",
|
description: "Slice out `0x[10 01 13]` from `0x[33 44 55 10 01 13]`",
|
||||||
example: " 0x[33 44 55 10 01 13] | bytes at ..<4",
|
example: "0x[33 44 55 10 01 13] | bytes at 3..5",
|
||||||
|
result: Some(Value::test_binary(vec![0x10, 0x01, 0x13])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Extract bytes from the start up to a specific index",
|
||||||
|
example: "0x[33 44 55 10 01 13 10] | bytes at ..4",
|
||||||
|
result: Some(Value::test_binary(vec![0x33, 0x44, 0x55, 0x10, 0x01])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Extract byte `0x[10]` using an exclusive end index",
|
||||||
|
example: "0x[33 44 55 10 01 13 10] | bytes at 3..<4",
|
||||||
|
result: Some(Value::test_binary(vec![0x10])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Extract bytes up to a negative index (inclusive)",
|
||||||
|
example: "0x[33 44 55 10 01 13 10] | bytes at ..-4",
|
||||||
result: Some(Value::test_binary(vec![0x33, 0x44, 0x55, 0x10])),
|
result: Some(Value::test_binary(vec![0x33, 0x44, 0x55, 0x10])),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description: "Slice bytes across multiple table columns",
|
||||||
"Or the characters from the beginning until ending index inside a table",
|
|
||||||
example: r#"[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB ColC"#,
|
example: r#"[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB ColC"#,
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||||
"ColA" => Value::test_binary(vec![0x11, 0x12, 0x13]),
|
"ColA" => Value::test_binary(vec![0x11, 0x12, 0x13]),
|
||||||
@ -120,34 +126,49 @@ impl Command for BytesAt {
|
|||||||
"ColC" => Value::test_binary(vec![0x18, 0x19]),
|
"ColC" => Value::test_binary(vec![0x18, 0x19]),
|
||||||
})])),
|
})])),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Extract the last three bytes using a negative start index",
|
||||||
|
example: "0x[33 44 55 10 01 13 10] | bytes at (-3)..",
|
||||||
|
result: Some(Value::test_binary(vec![0x01, 0x13, 0x10])),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
fn map_value(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
let range = &args.indexes;
|
let range = &args.range;
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
let len = val.len() as isize;
|
let len = val.len() as u64;
|
||||||
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
let start: u64 = range.absolute_start(len);
|
||||||
let end = if range.1 < 0 { range.1 + len } else { range.1 };
|
let _start: usize = match start.try_into() {
|
||||||
|
Ok(start) => start,
|
||||||
if start > end {
|
Err(_) => {
|
||||||
Value::binary(vec![], head)
|
let span = input.span();
|
||||||
} else {
|
return Value::error(
|
||||||
let val_iter = val.iter().skip(start as usize);
|
ShellError::UnsupportedInput {
|
||||||
Value::binary(
|
msg: format!(
|
||||||
if end == isize::MAX {
|
"Absolute start position {start} was too large for your system arch."
|
||||||
val_iter.copied().collect::<Vec<u8>>()
|
),
|
||||||
} else {
|
input: args.range.to_string(),
|
||||||
val_iter.take((end - start + 1) as usize).copied().collect()
|
msg_span: span,
|
||||||
|
input_span: span,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (start, end) = range.absolute_bounds(val.len());
|
||||||
|
let bytes: Vec<u8> = match end {
|
||||||
|
Bound::Unbounded => val[start..].into(),
|
||||||
|
Bound::Included(end) => val[start..=end].into(),
|
||||||
|
Bound::Excluded(end) => val[start..end].into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::binary(bytes, head)
|
||||||
}
|
}
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
|
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
ShellError::UnsupportedInput {
|
ShellError::UnsupportedInput {
|
||||||
msg: "Only binary values are supported".into(),
|
msg: "Only binary values are supported".into(),
|
||||||
@ -159,3 +180,14 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(BytesAt {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::{self, BufRead},
|
io::{self, BufRead},
|
||||||
@ -76,7 +77,7 @@ impl Command for BytesEndsWith {
|
|||||||
Ok(&[]) => break,
|
Ok(&[]) => break,
|
||||||
Ok(buf) => buf,
|
Ok(buf) => buf,
|
||||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(e.into_spanned(span).into()),
|
Err(e) => return Err(IoError::new(e.kind(), span, None).into()),
|
||||||
};
|
};
|
||||||
let len = buf.len();
|
let len = buf.len();
|
||||||
if len >= cap {
|
if len >= cap {
|
||||||
|
@ -9,6 +9,7 @@ mod length;
|
|||||||
mod remove;
|
mod remove;
|
||||||
mod replace;
|
mod replace;
|
||||||
mod reverse;
|
mod reverse;
|
||||||
|
mod split;
|
||||||
mod starts_with;
|
mod starts_with;
|
||||||
|
|
||||||
pub use add::BytesAdd;
|
pub use add::BytesAdd;
|
||||||
@ -22,4 +23,5 @@ pub use length::BytesLen;
|
|||||||
pub use remove::BytesRemove;
|
pub use remove::BytesRemove;
|
||||||
pub use replace::BytesReplace;
|
pub use replace::BytesReplace;
|
||||||
pub use reverse::BytesReverse;
|
pub use reverse::BytesReverse;
|
||||||
|
pub use split::BytesSplit;
|
||||||
pub use starts_with::BytesStartsWith;
|
pub use starts_with::BytesStartsWith;
|
||||||
|
109
crates/nu-command/src/bytes/split.rs
Normal file
109
crates/nu-command/src/bytes/split.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BytesSplit;
|
||||||
|
|
||||||
|
impl Command for BytesSplit {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"bytes split"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("bytes split")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::list(Type::Binary))])
|
||||||
|
.required(
|
||||||
|
"separator",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
|
||||||
|
"Bytes or string that the input will be split on (must be non-empty).",
|
||||||
|
)
|
||||||
|
.category(Category::Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Split input into multiple items using a separator."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["separate", "stream"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
example: r#"0x[66 6F 6F 20 62 61 72 20 62 61 7A 20] | bytes split 0x[20]"#,
|
||||||
|
description: "Split a binary value using a binary separator",
|
||||||
|
result: Some(Value::test_list(vec![
|
||||||
|
Value::test_binary("foo"),
|
||||||
|
Value::test_binary("bar"),
|
||||||
|
Value::test_binary("baz"),
|
||||||
|
Value::test_binary(""),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: r#"0x[61 2D 2D 62 2D 2D 63] | bytes split "--""#,
|
||||||
|
description: "Split a binary value using a string separator",
|
||||||
|
result: Some(Value::test_list(vec![
|
||||||
|
Value::test_binary("a"),
|
||||||
|
Value::test_binary("b"),
|
||||||
|
Value::test_binary("c"),
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let Spanned {
|
||||||
|
item: separator,
|
||||||
|
span,
|
||||||
|
}: Spanned<Vec<u8>> = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
if separator.is_empty() {
|
||||||
|
return Err(ShellError::IncorrectValue {
|
||||||
|
msg: "Separator can't be empty".into(),
|
||||||
|
val_span: span,
|
||||||
|
call_span: call.head,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let (split_read, md) = match input {
|
||||||
|
PipelineData::Value(Value::Binary { val, .. }, md) => (
|
||||||
|
ByteStream::read_binary(val, head, engine_state.signals().clone()).split(separator),
|
||||||
|
md,
|
||||||
|
),
|
||||||
|
PipelineData::ByteStream(stream, md) => (stream.split(separator), md),
|
||||||
|
input => {
|
||||||
|
let span = input.span().unwrap_or(head);
|
||||||
|
return Err(input.unsupported_input_error("bytes", span));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(split) = split_read {
|
||||||
|
Ok(split
|
||||||
|
.map(move |part| match part {
|
||||||
|
Ok(val) => Value::binary(val, head),
|
||||||
|
Err(err) => Value::error(err, head),
|
||||||
|
})
|
||||||
|
.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), md))
|
||||||
|
} else {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(BytesSplit {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -71,7 +72,7 @@ impl Command for BytesStartsWith {
|
|||||||
reader
|
reader
|
||||||
.take(pattern.len() as u64)
|
.take(pattern.len() as u64)
|
||||||
.read_to_end(&mut start)
|
.read_to_end(&mut start)
|
||||||
.err_span(span)?;
|
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||||
|
|
||||||
Ok(Value::bool(start == pattern, head).into_pipeline_data())
|
Ok(Value::bool(start == pattern, head).into_pipeline_data())
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,10 +16,16 @@ impl Command for SubCommand {
|
|||||||
(Type::Number, Type::Bool),
|
(Type::Number, Type::Bool),
|
||||||
(Type::String, Type::Bool),
|
(Type::String, Type::Bool),
|
||||||
(Type::Bool, Type::Bool),
|
(Type::Bool, Type::Bool),
|
||||||
|
(Type::Nothing, Type::Bool),
|
||||||
(Type::List(Box::new(Type::Any)), Type::table()),
|
(Type::List(Box::new(Type::Any)), Type::table()),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
])
|
])
|
||||||
|
.switch(
|
||||||
|
"relaxed",
|
||||||
|
"Relaxes conversion to also allow null and any strings.",
|
||||||
|
None,
|
||||||
|
)
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
@ -44,7 +50,10 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
into_bool(engine_state, stack, call, input)
|
let relaxed = call
|
||||||
|
.has_flag(engine_state, stack, "relaxed")
|
||||||
|
.unwrap_or(false);
|
||||||
|
into_bool(engine_state, stack, call, input, relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -95,22 +104,47 @@ impl Command for SubCommand {
|
|||||||
example: "'true' | into bool",
|
example: "'true' | into bool",
|
||||||
result: Some(Value::test_bool(true)),
|
result: Some(Value::test_bool(true)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "interpret a null as false",
|
||||||
|
example: "null | into bool --relaxed",
|
||||||
|
result: Some(Value::test_bool(false)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "interpret any non-false, non-zero string as true",
|
||||||
|
example: "'something' | into bool --relaxed",
|
||||||
|
result: Some(Value::test_bool(true)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct IntoBoolCmdArgument {
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
relaxed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for IntoBoolCmdArgument {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn into_bool(
|
fn into_bool(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
|
relaxed: bool,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths = Some(call.rest(engine_state, stack, 0)?).filter(|v| !v.is_empty());
|
||||||
let args = CellPathOnlyArgs::from(cell_paths);
|
let args = IntoBoolCmdArgument {
|
||||||
|
cell_paths,
|
||||||
|
relaxed,
|
||||||
|
};
|
||||||
operate(action, args, input, call.head, engine_state.signals())
|
operate(action, args, input, call.head, engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
fn strict_string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
||||||
match s.trim().to_ascii_lowercase().as_str() {
|
match s.trim().to_ascii_lowercase().as_str() {
|
||||||
"true" => Ok(true),
|
"true" => Ok(true),
|
||||||
"false" => Ok(false),
|
"false" => Ok(false),
|
||||||
@ -132,26 +166,31 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
fn action(input: &Value, args: &IntoBoolCmdArgument, span: Span) -> Value {
|
||||||
match input {
|
let err = || {
|
||||||
Value::Bool { .. } => input.clone(),
|
Value::error(
|
||||||
Value::Int { val, .. } => Value::bool(*val != 0, span),
|
ShellError::OnlySupportsThisInputType {
|
||||||
Value::Float { val, .. } => Value::bool(val.abs() >= f64::EPSILON, span),
|
exp_input_type: "bool, int, float or string".into(),
|
||||||
Value::String { val, .. } => match string_to_boolean(val, span) {
|
wrong_type: input.get_type().to_string(),
|
||||||
|
dst_span: span,
|
||||||
|
src_span: input.span(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match (input, args.relaxed) {
|
||||||
|
(Value::Error { .. } | Value::Bool { .. }, _) => input.clone(),
|
||||||
|
// In strict mode is this an error, while in relaxed this is just `false`
|
||||||
|
(Value::Nothing { .. }, false) => err(),
|
||||||
|
(Value::String { val, .. }, false) => match strict_string_to_boolean(val, span) {
|
||||||
Ok(val) => Value::bool(val, span),
|
Ok(val) => Value::bool(val, span),
|
||||||
Err(error) => Value::error(error, span),
|
Err(error) => Value::error(error, span),
|
||||||
},
|
},
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
_ => match input.coerce_bool() {
|
||||||
Value::Error { .. } => input.clone(),
|
Ok(val) => Value::bool(val, span),
|
||||||
other => Value::error(
|
Err(_) => err(),
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "bool, int, float or string".into(),
|
|
||||||
wrong_type: other.get_type().to_string(),
|
|
||||||
dst_span: span,
|
|
||||||
src_span: other.span(),
|
|
||||||
},
|
},
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,4 +204,32 @@ mod test {
|
|||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(SubCommand {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strict_handling() {
|
||||||
|
let span = Span::test_data();
|
||||||
|
let args = IntoBoolCmdArgument {
|
||||||
|
cell_paths: vec![].into(),
|
||||||
|
relaxed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(action(&Value::test_nothing(), &args, span).is_error());
|
||||||
|
assert!(action(&Value::test_string("abc"), &args, span).is_error());
|
||||||
|
assert!(action(&Value::test_string("true"), &args, span).is_true());
|
||||||
|
assert!(action(&Value::test_string("FALSE"), &args, span).is_false());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_relaxed_handling() {
|
||||||
|
let span = Span::test_data();
|
||||||
|
let args = IntoBoolCmdArgument {
|
||||||
|
cell_paths: vec![].into(),
|
||||||
|
relaxed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(action(&Value::test_nothing(), &args, span).is_false());
|
||||||
|
assert!(action(&Value::test_string("abc"), &args, span).is_true());
|
||||||
|
assert!(action(&Value::test_string("true"), &args, span).is_true());
|
||||||
|
assert!(action(&Value::test_string("FALSE"), &args, span).is_false());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ impl Command for IntoCellPath {
|
|||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("into cell-path")
|
Signature::build("into cell-path")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
|
(Type::CellPath, Type::CellPath),
|
||||||
(Type::Int, Type::CellPath),
|
(Type::Int, Type::CellPath),
|
||||||
(Type::List(Box::new(Type::Any)), Type::CellPath),
|
(Type::List(Box::new(Type::Any)), Type::CellPath),
|
||||||
(
|
(
|
||||||
@ -56,6 +57,13 @@ impl Command for IntoCellPath {
|
|||||||
members: vec![PathMember::test_int(5, false)],
|
members: vec![PathMember::test_int(5, false)],
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert cell path into cell path",
|
||||||
|
example: "5 | into cell-path | into cell-path",
|
||||||
|
result: Some(Value::test_cell_path(CellPath {
|
||||||
|
members: vec![PathMember::test_int(5, false)],
|
||||||
|
})),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string into cell path",
|
description: "Convert string into cell path",
|
||||||
example: "'some.path' | split row '.' | into cell-path",
|
example: "'some.path' | split row '.' | into cell-path",
|
||||||
@ -96,7 +104,7 @@ fn into_cell_path(call: &Call, input: PipelineData) -> Result<PipelineData, Shel
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::Value(value, _) => Ok(value_to_cell_path(&value, head)?.into_pipeline_data()),
|
PipelineData::Value(value, _) => Ok(value_to_cell_path(value, head)?.into_pipeline_data()),
|
||||||
PipelineData::ListStream(stream, ..) => {
|
PipelineData::ListStream(stream, ..) => {
|
||||||
let list: Vec<_> = stream.into_iter().collect();
|
let list: Vec<_> = stream.into_iter().collect();
|
||||||
Ok(list_to_cell_path(&list, head)?.into_pipeline_data())
|
Ok(list_to_cell_path(&list, head)?.into_pipeline_data())
|
||||||
@ -170,10 +178,11 @@ fn record_to_path_member(
|
|||||||
Ok(member)
|
Ok(member)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_cell_path(value: &Value, span: Span) -> Result<Value, ShellError> {
|
fn value_to_cell_path(value: Value, span: Span) -> Result<Value, ShellError> {
|
||||||
match value {
|
match value {
|
||||||
Value::Int { val, .. } => Ok(int_to_cell_path(*val, span)),
|
Value::CellPath { .. } => Ok(value),
|
||||||
Value::List { vals, .. } => list_to_cell_path(vals, span),
|
Value::Int { val, .. } => Ok(int_to_cell_path(val, span)),
|
||||||
|
Value::List { vals, .. } => list_to_cell_path(&vals, span),
|
||||||
other => Err(ShellError::OnlySupportsThisInputType {
|
other => Err(ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "int, list".into(),
|
exp_input_type: "int, list".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
@ -184,16 +193,11 @@ fn value_to_cell_path(value: &Value, span: Span) -> Result<Value, ShellError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_path_member(val: &Value, span: Span) -> Result<PathMember, ShellError> {
|
fn value_to_path_member(val: &Value, span: Span) -> Result<PathMember, ShellError> {
|
||||||
|
let val_span = val.span();
|
||||||
let member = match val {
|
let member = match val {
|
||||||
Value::Int {
|
Value::Int { val, .. } => int_to_path_member(*val, val_span)?,
|
||||||
val,
|
Value::String { val, .. } => PathMember::string(val.into(), false, val_span),
|
||||||
internal_span: span,
|
Value::Record { val, .. } => record_to_path_member(val, val_span, span)?,
|
||||||
} => int_to_path_member(*val, *span)?,
|
|
||||||
Value::String {
|
|
||||||
val,
|
|
||||||
internal_span: span,
|
|
||||||
} => PathMember::string(val.into(), false, *span),
|
|
||||||
Value::Record { val, internal_span } => record_to_path_member(val, *internal_span, span)?,
|
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::CantConvert {
|
return Err(ShellError::CantConvert {
|
||||||
to_type: "int or string".to_string(),
|
to_type: "int or string".to_string(),
|
||||||
|
@ -59,11 +59,17 @@ impl Command for SubCommand {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into datetime")
|
Signature::build("into datetime")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
|
(Type::Date, Type::Date),
|
||||||
(Type::Int, Type::Date),
|
(Type::Int, Type::Date),
|
||||||
(Type::String, Type::Date),
|
(Type::String, Type::Date),
|
||||||
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
|
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
(Type::record(), Type::record()),
|
(Type::record(), Type::record()),
|
||||||
|
(Type::Nothing, Type::table()),
|
||||||
|
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
||||||
|
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
|
||||||
|
// only applicable for --list flag
|
||||||
|
(Type::Any, Type::table()),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.named(
|
.named(
|
||||||
@ -204,7 +210,13 @@ impl Command for SubCommand {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert standard (seconds) unix timestamp to a UTC datetime",
|
description: "Convert standard (seconds) unix timestamp to a UTC datetime",
|
||||||
example: "1614434140 * 1_000_000_000 | into datetime",
|
example: "1614434140 | into datetime -f '%s'",
|
||||||
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
|
result: example_result_1(1614434140_000000000),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Using a datetime as input simply returns the value",
|
||||||
|
example: "2021-02-27T13:55:40 | into datetime",
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
result: example_result_1(1614434140_000000000),
|
result: example_result_1(1614434140_000000000),
|
||||||
},
|
},
|
||||||
@ -267,6 +279,11 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
let timezone = &args.zone_options;
|
let timezone = &args.zone_options;
|
||||||
let dateformat = &args.format_options;
|
let dateformat = &args.format_options;
|
||||||
|
|
||||||
|
// noop if the input is already a datetime
|
||||||
|
if matches!(input, Value::Date { .. }) {
|
||||||
|
return input.clone();
|
||||||
|
}
|
||||||
|
|
||||||
// Let's try dtparse first
|
// Let's try dtparse first
|
||||||
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
||||||
let span = input.span();
|
let span = input.span();
|
||||||
@ -636,6 +653,26 @@ mod tests {
|
|||||||
assert_eq!(actual, expected)
|
assert_eq!(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn takes_datetime() {
|
||||||
|
let timezone_option = Some(Spanned {
|
||||||
|
item: Zone::Local,
|
||||||
|
span: Span::test_data(),
|
||||||
|
});
|
||||||
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let expected = Value::date(
|
||||||
|
Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
let actual = action(&expected, &args, Span::test_data());
|
||||||
|
|
||||||
|
assert_eq!(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp_without_timezone() {
|
fn takes_timestamp_without_timezone() {
|
||||||
let date_str = Value::test_string("1614434140000000000");
|
let date_str = Value::test_string("1614434140000000000");
|
||||||
|
@ -22,6 +22,7 @@ impl Command for SubCommand {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into glob")
|
Signature::build("into glob")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
|
(Type::Glob, Type::Glob),
|
||||||
(Type::String, Type::Glob),
|
(Type::String, Type::Glob),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::String)),
|
Type::List(Box::new(Type::String)),
|
||||||
@ -64,6 +65,11 @@ impl Command for SubCommand {
|
|||||||
example: "'1234' | into glob",
|
example: "'1234' | into glob",
|
||||||
result: Some(Value::test_glob("1234")),
|
result: Some(Value::test_glob("1234")),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert glob to glob",
|
||||||
|
example: "'1234' | into glob | into glob",
|
||||||
|
result: Some(Value::test_glob("1234")),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert filepath to glob",
|
description: "convert filepath to glob",
|
||||||
example: "ls Cargo.toml | get name | into glob",
|
example: "ls Cargo.toml | get name | into glob",
|
||||||
@ -94,6 +100,7 @@ fn glob_helper(
|
|||||||
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::String { val, .. } => Value::glob(val.to_string(), false, span),
|
Value::String { val, .. } => Value::glob(val.to_string(), false, span),
|
||||||
|
Value::Glob { .. } => input.clone(),
|
||||||
x => Value::error(
|
x => Value::error(
|
||||||
ShellError::CantConvert {
|
ShellError::CantConvert {
|
||||||
to_type: String::from("glob"),
|
to_type: String::from("glob"),
|
||||||
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{into_code, Config};
|
use nu_protocol::{shell_error::into_code, Config};
|
||||||
use nu_utils::get_system_locale;
|
use nu_utils::get_system_locale;
|
||||||
use num_format::ToFormattedString;
|
use num_format::ToFormattedString;
|
||||||
|
|
||||||
@ -38,6 +38,7 @@ impl Command for SubCommand {
|
|||||||
(Type::Filesize, Type::String),
|
(Type::Filesize, Type::String),
|
||||||
(Type::Date, Type::String),
|
(Type::Date, Type::String),
|
||||||
(Type::Duration, Type::String),
|
(Type::Duration, Type::String),
|
||||||
|
(Type::Range, Type::String),
|
||||||
(
|
(
|
||||||
Type::List(Box::new(Type::Any)),
|
Type::List(Box::new(Type::Any)),
|
||||||
Type::List(Box::new(Type::String)),
|
Type::List(Box::new(Type::String)),
|
||||||
@ -127,8 +128,8 @@ impl Command for SubCommand {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert filesize to string",
|
description: "convert filesize to string",
|
||||||
example: "1KiB | into string",
|
example: "1kB | into string",
|
||||||
result: Some(Value::test_string("1.0 KiB")),
|
result: Some(Value::test_string("1.0 kB")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert duration to string",
|
description: "convert duration to string",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::parse_date_from_string;
|
use crate::parse_date_from_string;
|
||||||
|
use fancy_regex::{Regex, RegexBuilder};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::PipelineIterator;
|
use nu_protocol::PipelineIterator;
|
||||||
use regex::{Regex, RegexBuilder};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
let val_str = val.coerce_str().unwrap_or_default();
|
let val_str = val.coerce_str().unwrap_or_default();
|
||||||
|
|
||||||
// step 2: bounce string up against regexes
|
// step 2: bounce string up against regexes
|
||||||
if BOOLEAN_RE.is_match(&val_str) {
|
if BOOLEAN_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let bval = val_str
|
let bval = val_str
|
||||||
.parse::<bool>()
|
.parse::<bool>()
|
||||||
.map_err(|_| ShellError::CantConvert {
|
.map_err(|_| ShellError::CantConvert {
|
||||||
@ -156,12 +156,12 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::bool(bval, span))
|
Ok(Value::bool(bval, span))
|
||||||
} else if FLOAT_RE.is_match(&val_str) {
|
} else if FLOAT_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let fval = val_str
|
let fval = val_str
|
||||||
.parse::<f64>()
|
.parse::<f64>()
|
||||||
.map_err(|_| ShellError::CantConvert {
|
.map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "string".to_string(),
|
to_type: "float".to_string(),
|
||||||
from_type: "float".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
r#""{val_str}" does not represent a valid floating point value"#
|
r#""{val_str}" does not represent a valid floating point value"#
|
||||||
@ -169,12 +169,12 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::float(fval, span))
|
Ok(Value::float(fval, span))
|
||||||
} else if INTEGER_RE.is_match(&val_str) {
|
} else if INTEGER_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let ival = val_str
|
let ival = val_str
|
||||||
.parse::<i64>()
|
.parse::<i64>()
|
||||||
.map_err(|_| ShellError::CantConvert {
|
.map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "string".to_string(),
|
to_type: "int".to_string(),
|
||||||
from_type: "int".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
r#""{val_str}" does not represent a valid integer value"#
|
r#""{val_str}" does not represent a valid integer value"#
|
||||||
@ -186,15 +186,15 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
} else {
|
} else {
|
||||||
Ok(Value::int(ival, span))
|
Ok(Value::int(ival, span))
|
||||||
}
|
}
|
||||||
} else if INTEGER_WITH_DELIMS_RE.is_match(&val_str) {
|
} else if INTEGER_WITH_DELIMS_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let mut val_str = val_str.into_owned();
|
let mut val_str = val_str.into_owned();
|
||||||
val_str.retain(|x| !['_', ','].contains(&x));
|
val_str.retain(|x| !['_', ','].contains(&x));
|
||||||
|
|
||||||
let ival = val_str
|
let ival = val_str
|
||||||
.parse::<i64>()
|
.parse::<i64>()
|
||||||
.map_err(|_| ShellError::CantConvert {
|
.map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "string".to_string(),
|
to_type: "int".to_string(),
|
||||||
from_type: "int".to_string(),
|
from_type: "string".to_string(),
|
||||||
span,
|
span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
r#""{val_str}" does not represent a valid integer value"#
|
r#""{val_str}" does not represent a valid integer value"#
|
||||||
@ -206,7 +206,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
} else {
|
} else {
|
||||||
Ok(Value::int(ival, span))
|
Ok(Value::int(ival, span))
|
||||||
}
|
}
|
||||||
} else if DATETIME_DMY_RE.is_match(&val_str) {
|
} else if DATETIME_DMY_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "date".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
@ -217,7 +217,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::date(dt, span))
|
Ok(Value::date(dt, span))
|
||||||
} else if DATETIME_YMD_RE.is_match(&val_str) {
|
} else if DATETIME_YMD_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "date".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
@ -228,7 +228,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::date(dt, span))
|
Ok(Value::date(dt, span))
|
||||||
} else if DATETIME_YMDZ_RE.is_match(&val_str) {
|
} else if DATETIME_YMDZ_RE.is_match(&val_str).unwrap_or(false) {
|
||||||
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
|
||||||
to_type: "date".to_string(),
|
to_type: "date".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
@ -372,118 +372,154 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_float_parse() {
|
fn test_float_parse() {
|
||||||
// The regex should work on all these but nushell's float parser is more strict
|
// The regex should work on all these but nushell's float parser is more strict
|
||||||
assert!(FLOAT_RE.is_match("0.1"));
|
assert!(FLOAT_RE.is_match("0.1").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("3.0"));
|
assert!(FLOAT_RE.is_match("3.0").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("3.00001"));
|
assert!(FLOAT_RE.is_match("3.00001").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("-9.9990e-003"));
|
assert!(FLOAT_RE.is_match("-9.9990e-003").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("9.9990e+003"));
|
assert!(FLOAT_RE.is_match("9.9990e+003").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("9.9990E+003"));
|
assert!(FLOAT_RE.is_match("9.9990E+003").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("9.9990E+003"));
|
assert!(FLOAT_RE.is_match("9.9990E+003").unwrap());
|
||||||
assert!(FLOAT_RE.is_match(".5"));
|
assert!(FLOAT_RE.is_match(".5").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("2.5E-10"));
|
assert!(FLOAT_RE.is_match("2.5E-10").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("2.5e10"));
|
assert!(FLOAT_RE.is_match("2.5e10").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("NaN"));
|
assert!(FLOAT_RE.is_match("NaN").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("-NaN"));
|
assert!(FLOAT_RE.is_match("-NaN").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("-inf"));
|
assert!(FLOAT_RE.is_match("-inf").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("inf"));
|
assert!(FLOAT_RE.is_match("inf").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("-7e-05"));
|
assert!(FLOAT_RE.is_match("-7e-05").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("7e-05"));
|
assert!(FLOAT_RE.is_match("7e-05").unwrap());
|
||||||
assert!(FLOAT_RE.is_match("+7e+05"));
|
assert!(FLOAT_RE.is_match("+7e+05").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_int_parse() {
|
fn test_int_parse() {
|
||||||
assert!(INTEGER_RE.is_match("0"));
|
assert!(INTEGER_RE.is_match("0").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("1"));
|
assert!(INTEGER_RE.is_match("1").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("10"));
|
assert!(INTEGER_RE.is_match("10").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("100"));
|
assert!(INTEGER_RE.is_match("100").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("1000"));
|
assert!(INTEGER_RE.is_match("1000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("10000"));
|
assert!(INTEGER_RE.is_match("10000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("100000"));
|
assert!(INTEGER_RE.is_match("100000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("1000000"));
|
assert!(INTEGER_RE.is_match("1000000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("10000000"));
|
assert!(INTEGER_RE.is_match("10000000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("100000000"));
|
assert!(INTEGER_RE.is_match("100000000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("1000000000"));
|
assert!(INTEGER_RE.is_match("1000000000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("10000000000"));
|
assert!(INTEGER_RE.is_match("10000000000").unwrap());
|
||||||
assert!(INTEGER_RE.is_match("100000000000"));
|
assert!(INTEGER_RE.is_match("100000000000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1_000_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10_000_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000_000_000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("100_000_000_000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("100,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("100,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("100,000,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("100,000,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000,000,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("1,000,000,000").unwrap());
|
||||||
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000,000,000"));
|
assert!(INTEGER_WITH_DELIMS_RE.is_match("10,000,000,000").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bool_parse() {
|
fn test_bool_parse() {
|
||||||
assert!(BOOLEAN_RE.is_match("true"));
|
assert!(BOOLEAN_RE.is_match("true").unwrap());
|
||||||
assert!(BOOLEAN_RE.is_match("false"));
|
assert!(BOOLEAN_RE.is_match("false").unwrap());
|
||||||
assert!(!BOOLEAN_RE.is_match("1"));
|
assert!(!BOOLEAN_RE.is_match("1").unwrap());
|
||||||
assert!(!BOOLEAN_RE.is_match("0"));
|
assert!(!BOOLEAN_RE.is_match("0").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_ymdz_pattern() {
|
fn test_datetime_ymdz_pattern() {
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00Z"));
|
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00Z").unwrap());
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789Z"));
|
assert!(DATETIME_YMDZ_RE
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00+01:00"));
|
.is_match("2022-01-01T00:00:00.123456789Z")
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789+01:00"));
|
.unwrap());
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00-01:00"));
|
assert!(DATETIME_YMDZ_RE
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789-01:00"));
|
.is_match("2022-01-01T00:00:00+01:00")
|
||||||
assert!(DATETIME_YMDZ_RE.is_match("'2022-01-01T00:00:00Z'"));
|
.unwrap());
|
||||||
|
assert!(DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789+01:00")
|
||||||
|
.unwrap());
|
||||||
|
assert!(DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00-01:00")
|
||||||
|
.unwrap());
|
||||||
|
assert!(DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789-01:00")
|
||||||
|
.unwrap());
|
||||||
|
assert!(DATETIME_YMDZ_RE.is_match("'2022-01-01T00:00:00Z'").unwrap());
|
||||||
|
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00"));
|
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00").unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00."));
|
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.").unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789"));
|
assert!(!DATETIME_YMDZ_RE
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00+01"));
|
.is_match("2022-01-01T00:00:00.123456789")
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00+01:0"));
|
.unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00+1:00"));
|
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00+01").unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789+01"));
|
assert!(!DATETIME_YMDZ_RE
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789+01:0"));
|
.is_match("2022-01-01T00:00:00+01:0")
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789+1:00"));
|
.unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00-01"));
|
assert!(!DATETIME_YMDZ_RE
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00-01:0"));
|
.is_match("2022-01-01T00:00:00+1:00")
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00-1:00"));
|
.unwrap());
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789-01"));
|
assert!(!DATETIME_YMDZ_RE
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789-01:0"));
|
.is_match("2022-01-01T00:00:00.123456789+01")
|
||||||
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00.123456789-1:00"));
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789+01:0")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789+1:00")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE.is_match("2022-01-01T00:00:00-01").unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00-01:0")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00-1:00")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789-01")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789-01:0")
|
||||||
|
.unwrap());
|
||||||
|
assert!(!DATETIME_YMDZ_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.123456789-1:00")
|
||||||
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_ymd_pattern() {
|
fn test_datetime_ymd_pattern() {
|
||||||
assert!(DATETIME_YMD_RE.is_match("2022-01-01"));
|
assert!(DATETIME_YMD_RE.is_match("2022-01-01").unwrap());
|
||||||
assert!(DATETIME_YMD_RE.is_match("2022/01/01"));
|
assert!(DATETIME_YMD_RE.is_match("2022/01/01").unwrap());
|
||||||
assert!(DATETIME_YMD_RE.is_match("2022-01-01T00:00:00"));
|
assert!(DATETIME_YMD_RE.is_match("2022-01-01T00:00:00").unwrap());
|
||||||
assert!(DATETIME_YMD_RE.is_match("2022-01-01T00:00:00.000000000"));
|
assert!(DATETIME_YMD_RE
|
||||||
assert!(DATETIME_YMD_RE.is_match("'2022-01-01'"));
|
.is_match("2022-01-01T00:00:00.000000000")
|
||||||
|
.unwrap());
|
||||||
|
assert!(DATETIME_YMD_RE.is_match("'2022-01-01'").unwrap());
|
||||||
|
|
||||||
// The regex isn't this specific, but it would be nice if it were
|
// The regex isn't this specific, but it would be nice if it were
|
||||||
// assert!(!DATETIME_YMD_RE.is_match("2022-13-01"));
|
// assert!(!DATETIME_YMD_RE.is_match("2022-13-01").unwrap());
|
||||||
// assert!(!DATETIME_YMD_RE.is_match("2022-01-32"));
|
// assert!(!DATETIME_YMD_RE.is_match("2022-01-32").unwrap());
|
||||||
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T24:00:00"));
|
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T24:00:00").unwrap());
|
||||||
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T00:60:00"));
|
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T00:60:00").unwrap());
|
||||||
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T00:00:60"));
|
// assert!(!DATETIME_YMD_RE.is_match("2022-01-01T00:00:60").unwrap());
|
||||||
assert!(!DATETIME_YMD_RE.is_match("2022-01-01T00:00:00.0000000000"));
|
assert!(!DATETIME_YMD_RE
|
||||||
|
.is_match("2022-01-01T00:00:00.0000000000")
|
||||||
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_dmy_pattern() {
|
fn test_datetime_dmy_pattern() {
|
||||||
assert!(DATETIME_DMY_RE.is_match("31-12-2021"));
|
assert!(DATETIME_DMY_RE.is_match("31-12-2021").unwrap());
|
||||||
assert!(DATETIME_DMY_RE.is_match("01/01/2022"));
|
assert!(DATETIME_DMY_RE.is_match("01/01/2022").unwrap());
|
||||||
assert!(DATETIME_DMY_RE.is_match("15-06-2023 12:30"));
|
assert!(DATETIME_DMY_RE.is_match("15-06-2023 12:30").unwrap());
|
||||||
assert!(!DATETIME_DMY_RE.is_match("2022-13-01"));
|
assert!(!DATETIME_DMY_RE.is_match("2022-13-01").unwrap());
|
||||||
assert!(!DATETIME_DMY_RE.is_match("2022-01-32"));
|
assert!(!DATETIME_DMY_RE.is_match("2022-01-32").unwrap());
|
||||||
assert!(!DATETIME_DMY_RE.is_match("2022-01-01 24:00"));
|
assert!(!DATETIME_DMY_RE.is_match("2022-01-01 24:00").unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,11 +121,8 @@ fn split_cell_path(val: CellPath, span: Span) -> Result<Value, ShellError> {
|
|||||||
| PathMember::Int { optional, span, .. } => (optional, span),
|
| PathMember::Int { optional, span, .. } => (optional, span),
|
||||||
};
|
};
|
||||||
let value = match pm {
|
let value = match pm {
|
||||||
PathMember::String { val, .. } => Value::String { val, internal_span },
|
PathMember::String { val, .. } => Value::string(val, internal_span),
|
||||||
PathMember::Int { val, .. } => Value::Int {
|
PathMember::Int { val, .. } => Value::int(val as i64, internal_span),
|
||||||
val: val as i64,
|
|
||||||
internal_span,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
Self { value, optional }
|
Self { value, optional }
|
||||||
}
|
}
|
||||||
@ -142,10 +139,7 @@ fn split_cell_path(val: CellPath, span: Span) -> Result<Value, ShellError> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Value::List {
|
Ok(Value::list(members, span))
|
||||||
vals: members,
|
|
||||||
internal_span: span,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -196,13 +196,13 @@ fn action(
|
|||||||
PipelineData::ListStream(stream, _) => {
|
PipelineData::ListStream(stream, _) => {
|
||||||
insert_in_transaction(stream.into_iter(), span, table, signals)
|
insert_in_transaction(stream.into_iter(), span, table, signals)
|
||||||
}
|
}
|
||||||
PipelineData::Value(
|
PipelineData::Value(value @ Value::List { .. }, _) => {
|
||||||
Value::List {
|
let span = value.span();
|
||||||
vals,
|
let vals = value
|
||||||
internal_span,
|
.into_list()
|
||||||
},
|
.expect("Value matched as list above, but is not a list");
|
||||||
_,
|
insert_in_transaction(vals.into_iter(), span, table, signals)
|
||||||
) => insert_in_transaction(vals.into_iter(), internal_span, table, signals),
|
}
|
||||||
PipelineData::Value(val, _) => {
|
PipelineData::Value(val, _) => {
|
||||||
insert_in_transaction(std::iter::once(val), span, table, signals)
|
insert_in_transaction(std::iter::once(val), span, table, signals)
|
||||||
}
|
}
|
||||||
@ -353,7 +353,6 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
|||||||
|
|
||||||
// intentionally enumerated so that any future types get handled
|
// intentionally enumerated so that any future types get handled
|
||||||
Type::Any
|
Type::Any
|
||||||
| Type::Block
|
|
||||||
| Type::CellPath
|
| Type::CellPath
|
||||||
| Type::Closure
|
| Type::Closure
|
||||||
| Type::Custom(_)
|
| Type::Custom(_)
|
||||||
@ -361,7 +360,6 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
|||||||
| Type::List(_)
|
| Type::List(_)
|
||||||
| Type::Range
|
| Type::Range
|
||||||
| Type::Record(_)
|
| Type::Record(_)
|
||||||
| Type::Signature
|
|
||||||
| Type::Glob
|
| Type::Glob
|
||||||
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "sql".into(),
|
exp_input_type: "sql".into(),
|
||||||
|
@ -2,7 +2,10 @@ use super::definitions::{
|
|||||||
db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
||||||
db_index::DbIndex, db_table::DbTable,
|
db_index::DbIndex, db_table::DbTable,
|
||||||
};
|
};
|
||||||
use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value};
|
use nu_protocol::{
|
||||||
|
shell_error::io::IoError, CustomValue, PipelineData, Record, ShellError, Signals, Span,
|
||||||
|
Spanned, Value,
|
||||||
|
};
|
||||||
use rusqlite::{
|
use rusqlite::{
|
||||||
types::ValueRef, Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement,
|
types::ValueRef, Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement,
|
||||||
ToSql,
|
ToSql,
|
||||||
@ -38,24 +41,22 @@ impl SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> {
|
pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> {
|
||||||
let mut file = File::open(path).map_err(|e| ShellError::ReadingFile {
|
let mut file =
|
||||||
msg: e.to_string(),
|
File::open(path).map_err(|e| IoError::new(e.kind(), span, PathBuf::from(path)))?;
|
||||||
span,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut buf: [u8; 16] = [0; 16];
|
let mut buf: [u8; 16] = [0; 16];
|
||||||
file.read_exact(&mut buf)
|
file.read_exact(&mut buf)
|
||||||
.map_err(|e| ShellError::ReadingFile {
|
.map_err(|e| ShellError::Io(IoError::new(e.kind(), span, PathBuf::from(path))))
|
||||||
msg: e.to_string(),
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
.and_then(|_| {
|
.and_then(|_| {
|
||||||
if buf == SQLITE_MAGIC_BYTES {
|
if buf == SQLITE_MAGIC_BYTES {
|
||||||
Ok(SQLiteDatabase::new(path, signals))
|
Ok(SQLiteDatabase::new(path, signals))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::ReadingFile {
|
Err(ShellError::GenericError {
|
||||||
msg: "Not a SQLite file".into(),
|
error: "Not a SQLite file".into(),
|
||||||
span,
|
msg: format!("Could not read '{}' as SQLite file", path.display()),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3,8 +3,6 @@ mod humanize;
|
|||||||
mod list_timezone;
|
mod list_timezone;
|
||||||
mod now;
|
mod now;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod to_record;
|
|
||||||
mod to_table;
|
|
||||||
mod to_timezone;
|
mod to_timezone;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
@ -12,7 +10,5 @@ pub use date_::Date;
|
|||||||
pub use humanize::SubCommand as DateHumanize;
|
pub use humanize::SubCommand as DateHumanize;
|
||||||
pub use list_timezone::SubCommand as DateListTimezones;
|
pub use list_timezone::SubCommand as DateListTimezones;
|
||||||
pub use now::SubCommand as DateNow;
|
pub use now::SubCommand as DateNow;
|
||||||
pub use to_record::SubCommand as DateToRecord;
|
|
||||||
pub use to_table::SubCommand as DateToTable;
|
|
||||||
pub use to_timezone::SubCommand as DateToTimezone;
|
pub use to_timezone::SubCommand as DateToTimezone;
|
||||||
pub(crate) use utils::{generate_strftime_list, parse_date_from_string};
|
pub(crate) use utils::{generate_strftime_list, parse_date_from_string};
|
||||||
|
@ -1,148 +0,0 @@
|
|||||||
use crate::date::utils::parse_date_from_string;
|
|
||||||
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
|
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_protocol::{report_parse_warning, ParseWarning};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
impl Command for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"date to-record"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("date to-record")
|
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::Date, Type::record()),
|
|
||||||
(Type::String, Type::record()),
|
|
||||||
])
|
|
||||||
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
|
||||||
.category(Category::Deprecated)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Convert the date into a record."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["structured", "table"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
report_parse_warning(
|
|
||||||
&StateWorkingSet::new(engine_state),
|
|
||||||
&ParseWarning::DeprecatedWarning {
|
|
||||||
old_command: "date to-record".into(),
|
|
||||||
new_suggestion: "see `into record` command examples".into(),
|
|
||||||
span: head,
|
|
||||||
url: "`help into record`".into(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let head = call.head;
|
|
||||||
// This doesn't match explicit nulls
|
|
||||||
if matches!(input, PipelineData::Empty) {
|
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
|
||||||
}
|
|
||||||
input.map(move |value| helper(value, head), engine_state.signals())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Convert the current date into a record.",
|
|
||||||
example: "date now | date to-record",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert a date string into a record.",
|
|
||||||
example: "'2020-04-12T22:10:57.123+02:00' | date to-record",
|
|
||||||
result: Some(Value::test_record(record!(
|
|
||||||
"year" => Value::test_int(2020),
|
|
||||||
"month" => Value::test_int(4),
|
|
||||||
"day" => Value::test_int(12),
|
|
||||||
"hour" => Value::test_int(22),
|
|
||||||
"minute" => Value::test_int(10),
|
|
||||||
"second" => Value::test_int(57),
|
|
||||||
"nanosecond" => Value::test_int(123_000_000),
|
|
||||||
"timezone" => Value::test_string("+02:00"),
|
|
||||||
))),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert a date into a record.",
|
|
||||||
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-record",
|
|
||||||
result: Some(Value::test_record(record!(
|
|
||||||
"year" => Value::test_int(2020),
|
|
||||||
"month" => Value::test_int(4),
|
|
||||||
"day" => Value::test_int(12),
|
|
||||||
"hour" => Value::test_int(22),
|
|
||||||
"minute" => Value::test_int(10),
|
|
||||||
"second" => Value::test_int(57),
|
|
||||||
"nanosecond" => Value::test_int(0),
|
|
||||||
"timezone" => Value::test_string("+02:00"),
|
|
||||||
))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"year" => Value::int(date.year() as i64, head),
|
|
||||||
"month" => Value::int(date.month() as i64, head),
|
|
||||||
"day" => Value::int(date.day() as i64, head),
|
|
||||||
"hour" => Value::int(date.hour() as i64, head),
|
|
||||||
"minute" => Value::int(date.minute() as i64, head),
|
|
||||||
"second" => Value::int(date.second() as i64, head),
|
|
||||||
"nanosecond" => Value::int(date.nanosecond() as i64, head),
|
|
||||||
"timezone" => Value::string(date.offset().to_string(), head),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn helper(val: Value, head: Span) -> Value {
|
|
||||||
let span = val.span();
|
|
||||||
match val {
|
|
||||||
Value::String { val, .. } => match parse_date_from_string(&val, span) {
|
|
||||||
Ok(date) => parse_date_into_table(date, head),
|
|
||||||
Err(e) => e,
|
|
||||||
},
|
|
||||||
Value::Nothing { .. } => {
|
|
||||||
let now = Local::now();
|
|
||||||
let n = now.with_timezone(now.offset());
|
|
||||||
parse_date_into_table(n, head)
|
|
||||||
}
|
|
||||||
Value::Date { val, .. } => parse_date_into_table(val, head),
|
|
||||||
_ => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "date, string (that represents datetime), or nothing".into(),
|
|
||||||
wrong_type: val.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: span,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
use crate::date::utils::parse_date_from_string;
|
|
||||||
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
|
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_protocol::{report_parse_warning, ParseWarning};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
impl Command for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"date to-table"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("date to-table")
|
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::Date, Type::table()),
|
|
||||||
(Type::String, Type::table()),
|
|
||||||
])
|
|
||||||
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
|
||||||
.category(Category::Deprecated)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Convert the date into a structured table."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["structured"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
report_parse_warning(
|
|
||||||
&StateWorkingSet::new(engine_state),
|
|
||||||
&ParseWarning::DeprecatedWarning {
|
|
||||||
old_command: "date to-table".into(),
|
|
||||||
new_suggestion: "see `into record` command examples".into(),
|
|
||||||
span: head,
|
|
||||||
url: "`help into record`".into(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// This doesn't match explicit nulls
|
|
||||||
if matches!(input, PipelineData::Empty) {
|
|
||||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
|
||||||
}
|
|
||||||
input.map(move |value| helper(value, head), engine_state.signals())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Convert the current date into a table.",
|
|
||||||
example: "date now | date to-table",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert a given date into a table.",
|
|
||||||
example: "2020-04-12T22:10:57.000000789+02:00 | date to-table",
|
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record!(
|
|
||||||
"year" => Value::test_int(2020),
|
|
||||||
"month" => Value::test_int(4),
|
|
||||||
"day" => Value::test_int(12),
|
|
||||||
"hour" => Value::test_int(22),
|
|
||||||
"minute" => Value::test_int(10),
|
|
||||||
"second" => Value::test_int(57),
|
|
||||||
"nanosecond" => Value::test_int(789),
|
|
||||||
"timezone" => Value::test_string("+02:00".to_string()),
|
|
||||||
))])),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Convert a given date into a table.",
|
|
||||||
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-table",
|
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record!(
|
|
||||||
"year" => Value::test_int(2020),
|
|
||||||
"month" => Value::test_int(4),
|
|
||||||
"day" => Value::test_int(12),
|
|
||||||
"hour" => Value::test_int(22),
|
|
||||||
"minute" => Value::test_int(10),
|
|
||||||
"second" => Value::test_int(57),
|
|
||||||
"nanosecond" => Value::test_int(0),
|
|
||||||
"timezone" => Value::test_string("+02:00".to_string()),
|
|
||||||
))])),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
|
|
||||||
let record = record! {
|
|
||||||
"year" => Value::int(date.year() as i64, head),
|
|
||||||
"month" => Value::int(date.month() as i64, head),
|
|
||||||
"day" => Value::int(date.day() as i64, head),
|
|
||||||
"hour" => Value::int(date.hour() as i64, head),
|
|
||||||
"minute" => Value::int(date.minute() as i64, head),
|
|
||||||
"second" => Value::int(date.second() as i64, head),
|
|
||||||
"nanosecond" => Value::int(date.nanosecond() as i64, head),
|
|
||||||
"timezone" => Value::string(date.offset().to_string(), head),
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::list(vec![Value::record(record, head)], head)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn helper(val: Value, head: Span) -> Value {
|
|
||||||
let val_span = val.span();
|
|
||||||
match val {
|
|
||||||
Value::String { val, .. } => match parse_date_from_string(&val, val_span) {
|
|
||||||
Ok(date) => parse_date_into_table(date, head),
|
|
||||||
Err(e) => e,
|
|
||||||
},
|
|
||||||
Value::Nothing { .. } => {
|
|
||||||
let now = Local::now();
|
|
||||||
let n = now.with_timezone(now.offset());
|
|
||||||
parse_date_into_table(n, head)
|
|
||||||
}
|
|
||||||
Value::Date { val, .. } => parse_date_into_table(val, head),
|
|
||||||
_ => Value::error(
|
|
||||||
ShellError::OnlySupportsThisInputType {
|
|
||||||
exp_input_type: "date, string (that represents datetime), or nothing".into(),
|
|
||||||
wrong_type: val.get_type().to_string(),
|
|
||||||
dst_span: head,
|
|
||||||
src_span: val_span,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -154,7 +154,8 @@ fn get_arguments(
|
|||||||
eval_expression_fn,
|
eval_expression_fn,
|
||||||
);
|
);
|
||||||
let arg_type = "expr";
|
let arg_type = "expr";
|
||||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
let arg_value_name =
|
||||||
|
debug_string_without_formatting(engine_state, &evaluated_expression);
|
||||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||||
let evaled_span = evaluated_expression.span();
|
let evaled_span = evaluated_expression.span();
|
||||||
let arg_value_name_span_start = evaled_span.start as i64;
|
let arg_value_name_span_start = evaled_span.start as i64;
|
||||||
@ -174,7 +175,8 @@ fn get_arguments(
|
|||||||
let arg_type = "positional";
|
let arg_type = "positional";
|
||||||
let evaluated_expression =
|
let evaluated_expression =
|
||||||
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
||||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
let arg_value_name =
|
||||||
|
debug_string_without_formatting(engine_state, &evaluated_expression);
|
||||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||||
let evaled_span = evaluated_expression.span();
|
let evaled_span = evaluated_expression.span();
|
||||||
let arg_value_name_span_start = evaled_span.start as i64;
|
let arg_value_name_span_start = evaled_span.start as i64;
|
||||||
@ -193,7 +195,8 @@ fn get_arguments(
|
|||||||
let arg_type = "unknown";
|
let arg_type = "unknown";
|
||||||
let evaluated_expression =
|
let evaluated_expression =
|
||||||
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
||||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
let arg_value_name =
|
||||||
|
debug_string_without_formatting(engine_state, &evaluated_expression);
|
||||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||||
let evaled_span = evaluated_expression.span();
|
let evaled_span = evaluated_expression.span();
|
||||||
let arg_value_name_span_start = evaled_span.start as i64;
|
let arg_value_name_span_start = evaled_span.start as i64;
|
||||||
@ -212,7 +215,8 @@ fn get_arguments(
|
|||||||
let arg_type = "spread";
|
let arg_type = "spread";
|
||||||
let evaluated_expression =
|
let evaluated_expression =
|
||||||
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
|
||||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
let arg_value_name =
|
||||||
|
debug_string_without_formatting(engine_state, &evaluated_expression);
|
||||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||||
let evaled_span = evaluated_expression.span();
|
let evaled_span = evaluated_expression.span();
|
||||||
let arg_value_name_span_start = evaled_span.start as i64;
|
let arg_value_name_span_start = evaled_span.start as i64;
|
||||||
@ -245,7 +249,7 @@ fn get_expression_as_value(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_string_without_formatting(value: &Value) -> String {
|
pub fn debug_string_without_formatting(engine_state: &EngineState, value: &Value) -> String {
|
||||||
match value {
|
match value {
|
||||||
Value::Bool { val, .. } => val.to_string(),
|
Value::Bool { val, .. } => val.to_string(),
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
@ -259,19 +263,31 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
|||||||
Value::List { vals: val, .. } => format!(
|
Value::List { vals: val, .. } => format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
val.iter()
|
val.iter()
|
||||||
.map(debug_string_without_formatting)
|
.map(|v| debug_string_without_formatting(engine_state, v))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
),
|
),
|
||||||
Value::Record { val, .. } => format!(
|
Value::Record { val, .. } => format!(
|
||||||
"{{{}}}",
|
"{{{}}}",
|
||||||
val.iter()
|
val.iter()
|
||||||
.map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y)))
|
.map(|(x, y)| format!(
|
||||||
|
"{}: {}",
|
||||||
|
x,
|
||||||
|
debug_string_without_formatting(engine_state, y)
|
||||||
|
))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
),
|
),
|
||||||
//TODO: It would be good to drill deeper into closures.
|
Value::Closure { val, .. } => {
|
||||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id.get()),
|
let block = engine_state.get_block(val.block_id);
|
||||||
|
if let Some(span) = block.span {
|
||||||
|
let contents_bytes = engine_state.get_span_contents(span);
|
||||||
|
let contents_string = String::from_utf8_lossy(contents_bytes);
|
||||||
|
contents_string.to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
Value::Nothing { .. } => String::new(),
|
Value::Nothing { .. } => String::new(),
|
||||||
Value::Error { error, .. } => format!("{error:?}"),
|
Value::Error { error, .. } => format!("{error:?}"),
|
||||||
Value::Binary { val, .. } => format!("{val:?}"),
|
Value::Binary { val, .. } => format!("{val:?}"),
|
||||||
@ -280,7 +296,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
|||||||
// that critical here
|
// that critical here
|
||||||
Value::Custom { val, .. } => val
|
Value::Custom { val, .. } => val
|
||||||
.to_base_value(value.span())
|
.to_base_value(value.span())
|
||||||
.map(|val| debug_string_without_formatting(&val))
|
.map(|val| debug_string_without_formatting(engine_state, &val))
|
||||||
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
|
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ impl Command for DebugInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn all_columns(span: Span) -> Value {
|
fn all_columns(span: Span) -> Value {
|
||||||
let rk = RefreshKind::new()
|
let rk = RefreshKind::nothing()
|
||||||
.with_processes(ProcessRefreshKind::everything())
|
.with_processes(ProcessRefreshKind::everything())
|
||||||
.with_memory(MemoryRefreshKind::everything());
|
.with_memory(MemoryRefreshKind::everything());
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl Command for Inspect {
|
|||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
@ -40,7 +40,7 @@ impl Command for Inspect {
|
|||||||
|
|
||||||
let (cols, _rows) = terminal_size().unwrap_or((0, 0));
|
let (cols, _rows) = terminal_size().unwrap_or((0, 0));
|
||||||
|
|
||||||
let table = inspect_table::build_table(input_val, description, cols as usize);
|
let table = inspect_table::build_table(engine_state, input_val, description, cols as usize);
|
||||||
|
|
||||||
// Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell
|
// Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell
|
||||||
// tabular output. If we printed to stdout, nushell would get confused with two outputs.
|
// tabular output. If we printed to stdout, nushell would get confused with two outputs.
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
use crate::debug::inspect_table::{
|
// note: Seems like could be simplified
|
||||||
global_horizontal_char::SetHorizontalChar, set_widths::SetWidths,
|
// IMHO: it shall not take 300+ lines :)
|
||||||
};
|
|
||||||
|
use self::{global_horizontal_char::SetHorizontalChar, set_widths::SetWidths};
|
||||||
|
use nu_protocol::engine::EngineState;
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Value;
|
||||||
use nu_table::{string_width, string_wrap};
|
use nu_table::{string_width, string_wrap};
|
||||||
use tabled::{
|
use tabled::{
|
||||||
grid::config::ColoredConfig,
|
grid::config::ColoredConfig,
|
||||||
settings::{peaker::PriorityMax, width::Wrap, Settings, Style},
|
settings::{peaker::Priority, width::Wrap, Settings, Style},
|
||||||
Table,
|
Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
|
pub fn build_table(
|
||||||
let (head, mut data) = util::collect_input(value);
|
engine_state: &EngineState,
|
||||||
|
value: Value,
|
||||||
|
description: String,
|
||||||
|
termsize: usize,
|
||||||
|
) -> String {
|
||||||
|
let (head, mut data) = util::collect_input(engine_state, value);
|
||||||
let count_columns = head.len();
|
let count_columns = head.len();
|
||||||
data.insert(0, head);
|
data.insert(0, head);
|
||||||
|
|
||||||
@ -57,7 +64,7 @@ pub fn build_table(value: Value, description: String, termsize: usize) -> String
|
|||||||
Settings::default()
|
Settings::default()
|
||||||
.with(Style::rounded().corner_top_left('├').corner_top_right('┤'))
|
.with(Style::rounded().corner_top_left('├').corner_top_right('┤'))
|
||||||
.with(SetWidths(widths))
|
.with(SetWidths(widths))
|
||||||
.with(Wrap::new(width).priority(PriorityMax))
|
.with(Wrap::new(width).priority(Priority::max(true)))
|
||||||
.with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)),
|
.with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -192,10 +199,14 @@ fn push_empty_column(data: &mut Vec<Vec<String>>) {
|
|||||||
mod util {
|
mod util {
|
||||||
use crate::debug::explain::debug_string_without_formatting;
|
use crate::debug::explain::debug_string_without_formatting;
|
||||||
use nu_engine::get_columns;
|
use nu_engine::get_columns;
|
||||||
|
use nu_protocol::engine::EngineState;
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Value;
|
||||||
|
|
||||||
/// Try to build column names and a table grid.
|
/// Try to build column names and a table grid.
|
||||||
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<String>>) {
|
pub fn collect_input(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
value: Value,
|
||||||
|
) -> (Vec<String>, Vec<Vec<String>>) {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
@ -207,7 +218,7 @@ mod util {
|
|||||||
},
|
},
|
||||||
match vals
|
match vals
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| debug_string_without_formatting(&s))
|
.map(|s| debug_string_without_formatting(engine_state, &s))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
{
|
{
|
||||||
vals if vals.is_empty() => vec![],
|
vals if vals.is_empty() => vec![],
|
||||||
@ -217,7 +228,7 @@ mod util {
|
|||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let mut columns = get_columns(&vals);
|
let mut columns = get_columns(&vals);
|
||||||
let data = convert_records_to_dataset(&columns, vals);
|
let data = convert_records_to_dataset(engine_state, &columns, vals);
|
||||||
|
|
||||||
if columns.is_empty() {
|
if columns.is_empty() {
|
||||||
columns = vec![String::from("")];
|
columns = vec![String::from("")];
|
||||||
@ -229,7 +240,7 @@ mod util {
|
|||||||
let lines = val
|
let lines = val
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| Value::string(line.to_string(), span))
|
.map(|line| Value::string(line.to_string(), span))
|
||||||
.map(|val| vec![debug_string_without_formatting(&val)])
|
.map(|val| vec![debug_string_without_formatting(engine_state, &val)])
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(vec![String::from("")], lines)
|
(vec![String::from("")], lines)
|
||||||
@ -237,47 +248,59 @@ mod util {
|
|||||||
Value::Nothing { .. } => (vec![], vec![]),
|
Value::Nothing { .. } => (vec![], vec![]),
|
||||||
value => (
|
value => (
|
||||||
vec![String::from("")],
|
vec![String::from("")],
|
||||||
vec![vec![debug_string_without_formatting(&value)]],
|
vec![vec![debug_string_without_formatting(engine_state, &value)]],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_records_to_dataset(cols: &[String], records: Vec<Value>) -> Vec<Vec<String>> {
|
fn convert_records_to_dataset(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
cols: &[String],
|
||||||
|
records: Vec<Value>,
|
||||||
|
) -> Vec<Vec<String>> {
|
||||||
if !cols.is_empty() {
|
if !cols.is_empty() {
|
||||||
create_table_for_record(cols, &records)
|
create_table_for_record(engine_state, cols, &records)
|
||||||
} else if cols.is_empty() && records.is_empty() {
|
} else if cols.is_empty() && records.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else if cols.len() == records.len() {
|
} else if cols.len() == records.len() {
|
||||||
vec![records
|
vec![records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| debug_string_without_formatting(&s))
|
.map(|s| debug_string_without_formatting(engine_state, &s))
|
||||||
.collect()]
|
.collect()]
|
||||||
} else {
|
} else {
|
||||||
records
|
records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|record| vec![debug_string_without_formatting(&record)])
|
.map(|record| vec![debug_string_without_formatting(engine_state, &record)])
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_table_for_record(headers: &[String], items: &[Value]) -> Vec<Vec<String>> {
|
fn create_table_for_record(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
headers: &[String],
|
||||||
|
items: &[Value],
|
||||||
|
) -> Vec<Vec<String>> {
|
||||||
let mut data = vec![Vec::new(); items.len()];
|
let mut data = vec![Vec::new(); items.len()];
|
||||||
|
|
||||||
for (i, item) in items.iter().enumerate() {
|
for (i, item) in items.iter().enumerate() {
|
||||||
let row = record_create_row(headers, item);
|
let row = record_create_row(engine_state, headers, item);
|
||||||
data[i] = row;
|
data[i] = row;
|
||||||
}
|
}
|
||||||
|
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_create_row(headers: &[String], item: &Value) -> Vec<String> {
|
fn record_create_row(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
headers: &[String],
|
||||||
|
item: &Value,
|
||||||
|
) -> Vec<String> {
|
||||||
if let Value::Record { val, .. } = item {
|
if let Value::Record { val, .. } = item {
|
||||||
headers
|
headers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|col| {
|
.map(|col| {
|
||||||
val.get(col)
|
val.get(col)
|
||||||
.map(debug_string_without_formatting)
|
.map(|v| debug_string_without_formatting(engine_state, v))
|
||||||
.unwrap_or_else(String::new)
|
.unwrap_or_else(String::new)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
debugger::{Profiler, ProfilerOptions},
|
debugger::{DurationMode, Profiler, ProfilerOptions},
|
||||||
engine::Closure,
|
engine::Closure,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,6 +31,11 @@ impl Command for DebugProfile {
|
|||||||
Some('v'),
|
Some('v'),
|
||||||
)
|
)
|
||||||
.switch("lines", "Collect line numbers", Some('l'))
|
.switch("lines", "Collect line numbers", Some('l'))
|
||||||
|
.switch(
|
||||||
|
"duration-values",
|
||||||
|
"Report instruction duration as duration values rather than milliseconds",
|
||||||
|
Some('d'),
|
||||||
|
)
|
||||||
.named(
|
.named(
|
||||||
"max-depth",
|
"max-depth",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
@ -59,7 +64,7 @@ The output can be heavily customized. By default, the following columns are incl
|
|||||||
be shown with the --expand-source flag.
|
be shown with the --expand-source flag.
|
||||||
- pc : The index of the instruction within the block.
|
- pc : The index of the instruction within the block.
|
||||||
- instruction : The pretty printed instruction being evaluated.
|
- instruction : The pretty printed instruction being evaluated.
|
||||||
- duration_ms : How long it took to run the instruction in milliseconds.
|
- duration : How long it took to run the instruction.
|
||||||
- (optional) span : Span associated with the instruction. Can be viewed via the `view span`
|
- (optional) span : Span associated with the instruction. Can be viewed via the `view span`
|
||||||
command. Enabled with the --spans flag.
|
command. Enabled with the --spans flag.
|
||||||
- (optional) output : The output value of the instruction. Enabled with the --values flag.
|
- (optional) output : The output value of the instruction. Enabled with the --values flag.
|
||||||
@ -108,10 +113,15 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
|
|||||||
let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?;
|
let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?;
|
||||||
let collect_values = call.has_flag(engine_state, stack, "values")?;
|
let collect_values = call.has_flag(engine_state, stack, "values")?;
|
||||||
let collect_lines = call.has_flag(engine_state, stack, "lines")?;
|
let collect_lines = call.has_flag(engine_state, stack, "lines")?;
|
||||||
|
let duration_values = call.has_flag(engine_state, stack, "duration-values")?;
|
||||||
let max_depth = call
|
let max_depth = call
|
||||||
.get_flag(engine_state, stack, "max-depth")?
|
.get_flag(engine_state, stack, "max-depth")?
|
||||||
.unwrap_or(2);
|
.unwrap_or(2);
|
||||||
|
|
||||||
|
let duration_mode = match duration_values {
|
||||||
|
true => DurationMode::Value,
|
||||||
|
false => DurationMode::Milliseconds,
|
||||||
|
};
|
||||||
let profiler = Profiler::new(
|
let profiler = Profiler::new(
|
||||||
ProfilerOptions {
|
ProfilerOptions {
|
||||||
max_depth,
|
max_depth,
|
||||||
@ -122,6 +132,7 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
|
|||||||
collect_exprs: false,
|
collect_exprs: false,
|
||||||
collect_instructions: true,
|
collect_instructions: true,
|
||||||
collect_lines,
|
collect_lines,
|
||||||
|
duration_mode,
|
||||||
},
|
},
|
||||||
call.span(),
|
call.span(),
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
||||||
use nu_protocol::engine::Closure;
|
use nu_protocol::engine::Closure;
|
||||||
use std::time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TimeIt;
|
pub struct TimeIt;
|
||||||
|
@ -58,7 +58,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Interleave,
|
Interleave,
|
||||||
Items,
|
Items,
|
||||||
Join,
|
Join,
|
||||||
SplitBy,
|
|
||||||
Take,
|
Take,
|
||||||
Merge,
|
Merge,
|
||||||
MergeDeep,
|
MergeDeep,
|
||||||
@ -80,6 +79,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Skip,
|
Skip,
|
||||||
SkipUntil,
|
SkipUntil,
|
||||||
SkipWhile,
|
SkipWhile,
|
||||||
|
Slice,
|
||||||
Sort,
|
Sort,
|
||||||
SortBy,
|
SortBy,
|
||||||
SplitList,
|
SplitList,
|
||||||
@ -145,6 +145,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
HelpCommands,
|
HelpCommands,
|
||||||
HelpModules,
|
HelpModules,
|
||||||
HelpOperators,
|
HelpOperators,
|
||||||
|
HelpPipeAndRedirect,
|
||||||
HelpEscapes,
|
HelpEscapes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -241,7 +242,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Start,
|
Start,
|
||||||
Rm,
|
Rm,
|
||||||
Save,
|
Save,
|
||||||
Touch,
|
|
||||||
UTouch,
|
UTouch,
|
||||||
Glob,
|
Glob,
|
||||||
Watch,
|
Watch,
|
||||||
@ -276,8 +276,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
DateHumanize,
|
DateHumanize,
|
||||||
DateListTimezones,
|
DateListTimezones,
|
||||||
DateNow,
|
DateNow,
|
||||||
DateToRecord,
|
|
||||||
DateToTable,
|
|
||||||
DateToTimezone,
|
DateToTimezone,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -354,6 +352,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
ConfigFlatten,
|
ConfigFlatten,
|
||||||
ConfigMeta,
|
ConfigMeta,
|
||||||
ConfigReset,
|
ConfigReset,
|
||||||
|
ConfigUseColors,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Math
|
// Math
|
||||||
@ -380,6 +379,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
bind_command! {
|
bind_command! {
|
||||||
Bytes,
|
Bytes,
|
||||||
BytesLen,
|
BytesLen,
|
||||||
|
BytesSplit,
|
||||||
BytesStartsWith,
|
BytesStartsWith,
|
||||||
BytesEndsWith,
|
BytesEndsWith,
|
||||||
BytesReverse,
|
BytesReverse,
|
||||||
@ -404,6 +404,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
HttpPut,
|
HttpPut,
|
||||||
HttpOptions,
|
HttpOptions,
|
||||||
Port,
|
Port,
|
||||||
|
VersionCheck,
|
||||||
}
|
}
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Url,
|
Url,
|
||||||
|
15
crates/nu-command/src/env/config/config_.rs
vendored
15
crates/nu-command/src/env/config/config_.rs
vendored
@ -60,6 +60,8 @@ pub(super) fn start_editor(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
// Find the editor executable.
|
// Find the editor executable.
|
||||||
|
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
|
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
|
||||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
let cwd = engine_state.cwd(Some(stack))?;
|
||||||
@ -99,13 +101,22 @@ pub(super) fn start_editor(
|
|||||||
// Spawn the child process. On Unix, also put the child process to
|
// Spawn the child process. On Unix, also put the child process to
|
||||||
// foreground if we're in an interactive session.
|
// foreground if we're in an interactive session.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let child = ForegroundChild::spawn(command)?;
|
let child = ForegroundChild::spawn(command);
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let child = ForegroundChild::spawn(
|
let child = ForegroundChild::spawn(
|
||||||
command,
|
command,
|
||||||
engine_state.is_interactive,
|
engine_state.is_interactive,
|
||||||
&engine_state.pipeline_externals_state,
|
&engine_state.pipeline_externals_state,
|
||||||
)?;
|
);
|
||||||
|
|
||||||
|
let child = child.map_err(|err| {
|
||||||
|
IoError::new_with_additional_context(
|
||||||
|
err.kind(),
|
||||||
|
call.head,
|
||||||
|
None,
|
||||||
|
"Could not spawn foreground child",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// Wrap the output into a `PipelineData::ByteStream`.
|
// Wrap the output into a `PipelineData::ByteStream`.
|
||||||
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?;
|
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?;
|
||||||
|
13
crates/nu-command/src/env/config/config_nu.rs
vendored
13
crates/nu-command/src/env/config/config_nu.rs
vendored
@ -1,4 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::PipelineMetadata;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConfigNu;
|
pub struct ConfigNu;
|
||||||
@ -70,13 +71,21 @@ impl Command for ConfigNu {
|
|||||||
// `--default` flag handling
|
// `--default` flag handling
|
||||||
if default_flag {
|
if default_flag {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
return Ok(Value::string(nu_utils::get_default_config(), head).into_pipeline_data());
|
return Ok(Value::string(nu_utils::get_default_config(), head)
|
||||||
|
.into_pipeline_data_with_metadata(
|
||||||
|
PipelineMetadata::default()
|
||||||
|
.with_content_type("application/x-nuscript".to_string().into()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// `--doc` flag handling
|
// `--doc` flag handling
|
||||||
if doc_flag {
|
if doc_flag {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
return Ok(Value::string(nu_utils::get_doc_config(), head).into_pipeline_data());
|
return Ok(Value::string(nu_utils::get_doc_config(), head)
|
||||||
|
.into_pipeline_data_with_metadata(
|
||||||
|
PipelineMetadata::default()
|
||||||
|
.with_content_type("application/x-nuscript".to_string().into()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
super::config_::start_editor("config-path", engine_state, stack, call)
|
super::config_::start_editor("config-path", engine_state, stack, call)
|
||||||
|
53
crates/nu-command/src/env/config/config_reset.rs
vendored
53
crates/nu-command/src/env/config/config_reset.rs
vendored
@ -1,8 +1,9 @@
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use std::io::Write;
|
use nu_utils::{get_scaffold_config, get_scaffold_env};
|
||||||
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConfigReset;
|
pub struct ConfigReset;
|
||||||
@ -51,49 +52,57 @@ impl Command for ConfigReset {
|
|||||||
if !only_env {
|
if !only_env {
|
||||||
let mut nu_config = config_path.clone();
|
let mut nu_config = config_path.clone();
|
||||||
nu_config.push("config.nu");
|
nu_config.push("config.nu");
|
||||||
let config_file = get_default_config();
|
let config_file = get_scaffold_config();
|
||||||
if !no_backup {
|
if !no_backup {
|
||||||
let mut backup_path = config_path.clone();
|
let mut backup_path = config_path.clone();
|
||||||
backup_path.push(format!(
|
backup_path.push(format!(
|
||||||
"oldconfig-{}.nu",
|
"oldconfig-{}.nu",
|
||||||
Local::now().format("%F-%H-%M-%S"),
|
Local::now().format("%F-%H-%M-%S"),
|
||||||
));
|
));
|
||||||
if std::fs::rename(nu_config.clone(), backup_path).is_err() {
|
if let Err(err) = std::fs::rename(nu_config.clone(), &backup_path) {
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: "config.nu could not be backed up".into(),
|
err.kind(),
|
||||||
span,
|
span,
|
||||||
});
|
PathBuf::from(backup_path),
|
||||||
|
"config.nu could not be backed up",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(mut file) = std::fs::File::create(nu_config) {
|
if let Ok(mut file) = std::fs::File::create(&nu_config) {
|
||||||
if writeln!(&mut file, "{config_file}").is_err() {
|
if let Err(err) = writeln!(&mut file, "{config_file}") {
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: "config.nu could not be written to".into(),
|
err.kind(),
|
||||||
span,
|
span,
|
||||||
});
|
PathBuf::from(nu_config),
|
||||||
|
"config.nu could not be written to",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !only_nu {
|
if !only_nu {
|
||||||
let mut env_config = config_path.clone();
|
let mut env_config = config_path.clone();
|
||||||
env_config.push("env.nu");
|
env_config.push("env.nu");
|
||||||
let config_file = get_default_env();
|
let config_file = get_scaffold_env();
|
||||||
if !no_backup {
|
if !no_backup {
|
||||||
let mut backup_path = config_path.clone();
|
let mut backup_path = config_path.clone();
|
||||||
backup_path.push(format!("oldenv-{}.nu", Local::now().format("%F-%H-%M-%S"),));
|
backup_path.push(format!("oldenv-{}.nu", Local::now().format("%F-%H-%M-%S"),));
|
||||||
if std::fs::rename(env_config.clone(), backup_path).is_err() {
|
if let Err(err) = std::fs::rename(env_config.clone(), &backup_path) {
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: "env.nu could not be backed up".into(),
|
err.kind(),
|
||||||
span,
|
span,
|
||||||
});
|
PathBuf::from(backup_path),
|
||||||
|
"env.nu could not be backed up",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(mut file) = std::fs::File::create(env_config) {
|
if let Ok(mut file) = std::fs::File::create(&env_config) {
|
||||||
if writeln!(&mut file, "{config_file}").is_err() {
|
if let Err(err) = writeln!(&mut file, "{config_file}") {
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: "env.nu could not be written to".into(),
|
err.kind(),
|
||||||
span,
|
span,
|
||||||
});
|
PathBuf::from(env_config),
|
||||||
|
"env.nu could not be written to",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
crates/nu-command/src/env/config/config_use_colors.rs
vendored
Normal file
41
crates/nu-command/src/env/config/config_use_colors.rs
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ConfigUseColors;
|
||||||
|
|
||||||
|
impl Command for ConfigUseColors {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"config use-colors"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.category(Category::Env)
|
||||||
|
.input_output_type(Type::Nothing, Type::Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Get the configuration for color output."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
r#"Use this command instead of checking `$env.config.use_ansi_coloring` to properly handle the "auto" setting, including environment variables that influence its behavior."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let use_ansi_coloring = engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state);
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
Value::bool(use_ansi_coloring, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
2
crates/nu-command/src/env/config/mod.rs
vendored
2
crates/nu-command/src/env/config/mod.rs
vendored
@ -3,9 +3,11 @@ mod config_env;
|
|||||||
mod config_flatten;
|
mod config_flatten;
|
||||||
mod config_nu;
|
mod config_nu;
|
||||||
mod config_reset;
|
mod config_reset;
|
||||||
|
mod config_use_colors;
|
||||||
|
|
||||||
pub use config_::ConfigMeta;
|
pub use config_::ConfigMeta;
|
||||||
pub use config_env::ConfigEnv;
|
pub use config_env::ConfigEnv;
|
||||||
pub use config_flatten::ConfigFlatten;
|
pub use config_flatten::ConfigFlatten;
|
||||||
pub use config_nu::ConfigNu;
|
pub use config_nu::ConfigNu;
|
||||||
pub use config_reset::ConfigReset;
|
pub use config_reset::ConfigReset;
|
||||||
|
pub use config_use_colors::ConfigUseColors;
|
||||||
|
4
crates/nu-command/src/env/export_env.rs
vendored
4
crates/nu-command/src/env/export_env.rs
vendored
@ -57,8 +57,10 @@ impl Command for ExportEnv {
|
|||||||
|
|
||||||
let eval_block = get_eval_block(engine_state);
|
let eval_block = get_eval_block(engine_state);
|
||||||
|
|
||||||
let _ = eval_block(engine_state, &mut callee_stack, block, input);
|
// Run the block (discard the result)
|
||||||
|
let _ = eval_block(engine_state, &mut callee_stack, block, input)?;
|
||||||
|
|
||||||
|
// Merge the block's environment to the current stack
|
||||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
|
3
crates/nu-command/src/env/load_env.rs
vendored
3
crates/nu-command/src/env/load_env.rs
vendored
@ -17,6 +17,9 @@ impl Command for LoadEnv {
|
|||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::record(), Type::Nothing),
|
(Type::record(), Type::Nothing),
|
||||||
(Type::Nothing, Type::Nothing),
|
(Type::Nothing, Type::Nothing),
|
||||||
|
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
||||||
|
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
|
||||||
|
(Type::Any, Type::Nothing),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.optional(
|
.optional(
|
||||||
|
1
crates/nu-command/src/env/mod.rs
vendored
1
crates/nu-command/src/env/mod.rs
vendored
@ -9,6 +9,7 @@ pub use config::ConfigFlatten;
|
|||||||
pub use config::ConfigMeta;
|
pub use config::ConfigMeta;
|
||||||
pub use config::ConfigNu;
|
pub use config::ConfigNu;
|
||||||
pub use config::ConfigReset;
|
pub use config::ConfigReset;
|
||||||
|
pub use config::ConfigUseColors;
|
||||||
pub use export_env::ExportEnv;
|
pub use export_env::ExportEnv;
|
||||||
pub use load_env::LoadEnv;
|
pub use load_env::LoadEnv;
|
||||||
pub use source_env::SourceEnv;
|
pub use source_env::SourceEnv;
|
||||||
|
30
crates/nu-command/src/env/source_env.rs
vendored
30
crates/nu-command/src/env/source_env.rs
vendored
@ -2,7 +2,7 @@ use nu_engine::{
|
|||||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
||||||
redirect_env,
|
redirect_env,
|
||||||
};
|
};
|
||||||
use nu_protocol::{engine::CommandType, BlockId};
|
use nu_protocol::{engine::CommandType, shell_error::io::IoError, BlockId};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Source a file for environment variables.
|
/// Source a file for environment variables.
|
||||||
@ -19,8 +19,8 @@ impl Command for SourceEnv {
|
|||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.required(
|
.required(
|
||||||
"filename",
|
"filename",
|
||||||
SyntaxShape::String, // type is string to avoid automatically canonicalizing the path
|
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]), // type is string to avoid automatically canonicalizing the path
|
||||||
"The filepath to the script file to source the environment from.",
|
"The filepath to the script file to source the environment from (`null` for no-op).",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
@ -45,6 +45,10 @@ impl Command for SourceEnv {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if call.get_parser_info(caller_stack, "noop").is_some() {
|
||||||
|
return Ok(PipelineData::empty());
|
||||||
|
}
|
||||||
|
|
||||||
let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||||
|
|
||||||
// Note: this hidden positional is the block_id that corresponded to the 0th position
|
// Note: this hidden positional is the block_id that corresponded to the 0th position
|
||||||
@ -61,10 +65,11 @@ impl Command for SourceEnv {
|
|||||||
)? {
|
)? {
|
||||||
PathBuf::from(&path)
|
PathBuf::from(&path)
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::FileNotFound {
|
return Err(ShellError::Io(IoError::new(
|
||||||
file: source_filename.item,
|
std::io::ErrorKind::NotFound,
|
||||||
span: source_filename.span,
|
source_filename.span,
|
||||||
});
|
PathBuf::from(source_filename.item),
|
||||||
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(parent) = file_path.parent() {
|
if let Some(parent) = file_path.parent() {
|
||||||
@ -99,10 +104,17 @@ impl Command for SourceEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Sources the environment from foo.nu in the current context",
|
description: "Sources the environment from foo.nu in the current context",
|
||||||
example: r#"source-env foo.nu"#,
|
example: r#"source-env foo.nu"#,
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Sourcing `null` is a no-op.",
|
||||||
|
example: r#"source-env null"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,12 @@ mod test_examples {
|
|||||||
signature.operates_on_cell_paths(),
|
signature.operates_on_cell_paths(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
|
check_example_evaluates_to_expected_output(
|
||||||
|
cmd.name(),
|
||||||
|
&example,
|
||||||
|
cwd.as_path(),
|
||||||
|
&mut engine_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_signature_input_output_types_entries_have_examples(
|
check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::shell_error::{self, io::IoError};
|
||||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -22,10 +25,6 @@ impl Command for Cd {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
|
.switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
|
||||||
.optional("path", SyntaxShape::Directory, "The path to change to.")
|
.optional("path", SyntaxShape::Directory, "The path to change to.")
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::Nothing, Type::Nothing),
|
|
||||||
(Type::String, Type::Nothing),
|
|
||||||
])
|
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::FileSystem)
|
.category(Category::FileSystem)
|
||||||
}
|
}
|
||||||
@ -77,25 +76,39 @@ impl Command for Cd {
|
|||||||
if physical {
|
if physical {
|
||||||
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(ShellError::NotADirectory { span: v.span });
|
return Err(shell_error::io::IoError::new(
|
||||||
|
shell_error::io::ErrorKind::NotADirectory,
|
||||||
|
v.span,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
};
|
};
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::DirectoryNotFound {
|
return Err(shell_error::io::IoError::new(
|
||||||
dir: path_no_whitespace.to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: v.span,
|
v.span,
|
||||||
});
|
PathBuf::from(path_no_whitespace),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(ShellError::DirectoryNotFound {
|
return Err(shell_error::io::IoError::new(
|
||||||
dir: path_no_whitespace.to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: v.span,
|
v.span,
|
||||||
});
|
PathBuf::from(path_no_whitespace),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
};
|
};
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(ShellError::NotADirectory { span: v.span });
|
return Err(shell_error::io::IoError::new(
|
||||||
|
shell_error::io::ErrorKind::NotADirectory,
|
||||||
|
v.span,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
};
|
};
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
@ -117,13 +130,9 @@ impl Command for Cd {
|
|||||||
stack.set_cwd(path)?;
|
stack.set_cwd(path)?;
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
|
PermissionResult::PermissionDenied(_) => {
|
||||||
msg: format!(
|
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
||||||
"Cannot change directory to {}: {}",
|
}
|
||||||
path.to_string_lossy(),
|
|
||||||
reason
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +165,7 @@ fn du_for_one_pattern(
|
|||||||
span: Span,
|
span: Span,
|
||||||
signals: &Signals,
|
signals: &Signals,
|
||||||
) -> Result<impl Iterator<Item = Value> + Send, ShellError> {
|
) -> Result<impl Iterator<Item = Value> + Send, ShellError> {
|
||||||
|
let signals_clone = signals.clone();
|
||||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||||
Pattern::new(x.item.as_ref())
|
Pattern::new(x.item.as_ref())
|
||||||
.map(Some)
|
.map(Some)
|
||||||
@ -174,7 +175,7 @@ fn du_for_one_pattern(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut paths = match args.path {
|
let paths = match args.path {
|
||||||
Some(p) => nu_engine::glob_from(&p, current_dir, span, None),
|
Some(p) => nu_engine::glob_from(&p, current_dir, span, None),
|
||||||
// The * pattern should never fail.
|
// The * pattern should never fail.
|
||||||
None => nu_engine::glob_from(
|
None => nu_engine::glob_from(
|
||||||
@ -202,22 +203,22 @@ fn du_for_one_pattern(
|
|||||||
long,
|
long,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output: Vec<Value> = vec![];
|
Ok(paths.filter_map(move |p| match p {
|
||||||
for p in paths.by_ref() {
|
|
||||||
match p {
|
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
if a.is_dir() {
|
if a.is_dir() {
|
||||||
output.push(DirInfo::new(a, ¶ms, max_depth, span, signals)?.into());
|
match DirInfo::new(a, ¶ms, max_depth, span, &signals_clone) {
|
||||||
} else if let Ok(v) = FileInfo::new(a, deref, span, params.long) {
|
Ok(v) => Some(Value::from(v)),
|
||||||
output.push(v.into());
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
Err(e) => {
|
match FileInfo::new(a, deref, span, params.long) {
|
||||||
output.push(Value::error(e, span));
|
Ok(v) => Some(Value::from(v)),
|
||||||
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(output.into_iter())
|
Err(e) => Some(Value::error(e, span)),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -5,7 +5,7 @@ use nu_engine::glob_from;
|
|||||||
use nu_engine::{command_prelude::*, env::current_dir};
|
use nu_engine::{command_prelude::*, env::current_dir};
|
||||||
use nu_glob::MatchOptions;
|
use nu_glob::MatchOptions;
|
||||||
use nu_path::{expand_path_with, expand_to_real_path};
|
use nu_path::{expand_path_with, expand_to_real_path};
|
||||||
use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals};
|
use nu_protocol::{shell_error::io::IoError, DataSource, NuGlob, PipelineMetadata, Signals};
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@ -33,8 +33,6 @@ struct Args {
|
|||||||
call_span: Span,
|
call_span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GLOB_CHARS: &[char] = &['*', '?', '['];
|
|
||||||
|
|
||||||
impl Command for Ls {
|
impl Command for Ls {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"ls"
|
"ls"
|
||||||
@ -256,10 +254,12 @@ fn ls_for_one_pattern(
|
|||||||
if let Some(path) = pattern_arg {
|
if let Some(path) = pattern_arg {
|
||||||
// it makes no sense to list an empty string.
|
// it makes no sense to list an empty string.
|
||||||
if path.item.as_ref().is_empty() {
|
if path.item.as_ref().is_empty() {
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: "empty string('') directory or file does not exist".to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: path.span,
|
path.span,
|
||||||
});
|
PathBuf::from(path.item.to_string()),
|
||||||
|
"empty string('') directory or file does not exist",
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
match path.item {
|
match path.item {
|
||||||
NuGlob::DoNotExpand(p) => Some(Spanned {
|
NuGlob::DoNotExpand(p) => Some(Spanned {
|
||||||
@ -285,13 +285,10 @@ fn ls_for_one_pattern(
|
|||||||
nu_path::expand_path_with(pat.item.as_ref(), &cwd, pat.item.is_expand());
|
nu_path::expand_path_with(pat.item.as_ref(), &cwd, pat.item.is_expand());
|
||||||
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
|
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
|
||||||
if !directory && tmp_expanded.is_dir() {
|
if !directory && tmp_expanded.is_dir() {
|
||||||
if read_dir(&tmp_expanded, p_tag, use_threads)?
|
if read_dir(tmp_expanded, p_tag, use_threads)?.next().is_none() {
|
||||||
.next()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
return Ok(Value::test_nothing().into_pipeline_data());
|
return Ok(Value::test_nothing().into_pipeline_data());
|
||||||
}
|
}
|
||||||
just_read_dir = !(pat.item.is_expand() && pat.item.as_ref().contains(GLOB_CHARS));
|
just_read_dir = !(pat.item.is_expand() && nu_glob::is_glob(pat.item.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's absolute path if:
|
// it's absolute path if:
|
||||||
@ -307,7 +304,7 @@ fn ls_for_one_pattern(
|
|||||||
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
|
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
|
||||||
if directory {
|
if directory {
|
||||||
(NuGlob::Expand(".".to_string()), false)
|
(NuGlob::Expand(".".to_string()), false)
|
||||||
} else if read_dir(&cwd, p_tag, use_threads)?.next().is_none() {
|
} else if read_dir(cwd.clone(), p_tag, use_threads)?.next().is_none() {
|
||||||
return Ok(Value::test_nothing().into_pipeline_data());
|
return Ok(Value::test_nothing().into_pipeline_data());
|
||||||
} else {
|
} else {
|
||||||
(NuGlob::Expand("*".to_string()), false)
|
(NuGlob::Expand("*".to_string()), false)
|
||||||
@ -316,10 +313,11 @@ fn ls_for_one_pattern(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hidden_dir_specified = is_hidden_dir(pattern_arg.as_ref());
|
let hidden_dir_specified = is_hidden_dir(pattern_arg.as_ref());
|
||||||
|
|
||||||
let path = pattern_arg.into_spanned(p_tag);
|
let path = pattern_arg.into_spanned(p_tag);
|
||||||
let (prefix, paths) = if just_read_dir {
|
let (prefix, paths) = if just_read_dir {
|
||||||
let expanded = nu_path::expand_path_with(path.item.as_ref(), &cwd, path.item.is_expand());
|
let expanded = nu_path::expand_path_with(path.item.as_ref(), &cwd, path.item.is_expand());
|
||||||
let paths = read_dir(&expanded, p_tag, use_threads)?;
|
let paths = read_dir(expanded.clone(), p_tag, use_threads)?;
|
||||||
// just need to read the directory, so prefix is path itself.
|
// just need to read the directory, so prefix is path itself.
|
||||||
(Some(expanded), paths)
|
(Some(expanded), paths)
|
||||||
} else {
|
} else {
|
||||||
@ -351,14 +349,24 @@ fn ls_for_one_pattern(
|
|||||||
let signals_clone = signals.clone();
|
let signals_clone = signals.clone();
|
||||||
|
|
||||||
let pool = if use_threads {
|
let pool = if use_threads {
|
||||||
let count = std::thread::available_parallelism()?.get();
|
let count = std::thread::available_parallelism()
|
||||||
|
.map_err(|err| {
|
||||||
|
IoError::new_with_additional_context(
|
||||||
|
err.kind(),
|
||||||
|
call_span,
|
||||||
|
None,
|
||||||
|
"Could not get available parallelism",
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.get();
|
||||||
create_pool(count)?
|
create_pool(count)?
|
||||||
} else {
|
} else {
|
||||||
create_pool(1)?
|
create_pool(1)?
|
||||||
};
|
};
|
||||||
|
|
||||||
pool.install(|| {
|
pool.install(|| {
|
||||||
paths_peek
|
rayon::spawn(move || {
|
||||||
|
let result = paths_peek
|
||||||
.par_bridge()
|
.par_bridge()
|
||||||
.filter_map(move |x| match x {
|
.filter_map(move |x| match x {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
@ -390,8 +398,9 @@ fn ls_for_one_pattern(
|
|||||||
if let Ok(remainder) = path.strip_prefix(prefix) {
|
if let Ok(remainder) = path.strip_prefix(prefix) {
|
||||||
if directory {
|
if directory {
|
||||||
// When the path is the same as the cwd, path_diff should be "."
|
// When the path is the same as the cwd, path_diff should be "."
|
||||||
let path_diff =
|
let path_diff = if let Some(path_diff_not_dot) =
|
||||||
if let Some(path_diff_not_dot) = diff_paths(&path, &cwd) {
|
diff_paths(&path, &cwd)
|
||||||
|
{
|
||||||
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
||||||
if path_diff_not_dot.is_empty() {
|
if path_diff_not_dot.is_empty() {
|
||||||
".".to_string()
|
".".to_string()
|
||||||
@ -458,14 +467,19 @@ fn ls_for_one_pattern(
|
|||||||
inner: vec![],
|
inner: vec![],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.map_err(|err| ShellError::GenericError {
|
.map_err(|err| ShellError::GenericError {
|
||||||
error: "Unable to create a rayon pool".into(),
|
error: "Unable to create a rayon pool".into(),
|
||||||
msg: err.to_string(),
|
msg: err.to_string(),
|
||||||
span: Some(call_span),
|
span: Some(call_span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})?;
|
});
|
||||||
|
|
||||||
|
if let Err(error) = result {
|
||||||
|
let _ = tx.send(Value::error(error, call_span));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Ok(rx
|
Ok(rx
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -904,14 +918,12 @@ mod windows_helper {
|
|||||||
&mut find_data,
|
&mut find_data,
|
||||||
) {
|
) {
|
||||||
Ok(_) => Ok(find_data),
|
Ok(_) => Ok(find_data),
|
||||||
Err(e) => Err(ShellError::ReadingFile {
|
Err(e) => Err(ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: format!(
|
std::io::ErrorKind::Other,
|
||||||
"Could not read metadata for '{}':\n '{}'",
|
|
||||||
filename.to_string_lossy(),
|
|
||||||
e
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
}),
|
PathBuf::from(filename),
|
||||||
|
format!("Could not read metadata: {e}"),
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -944,28 +956,17 @@ mod windows_helper {
|
|||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn read_dir(
|
fn read_dir(
|
||||||
f: &Path,
|
f: PathBuf,
|
||||||
span: Span,
|
span: Span,
|
||||||
use_threads: bool,
|
use_threads: bool,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<PathBuf, ShellError>> + Send>, ShellError> {
|
) -> Result<Box<dyn Iterator<Item = Result<PathBuf, ShellError>> + Send>, ShellError> {
|
||||||
let items = f
|
let items = f
|
||||||
.read_dir()
|
.read_dir()
|
||||||
.map_err(|error| {
|
.map_err(|err| IoError::new(err.kind(), span, f.clone()))?
|
||||||
if error.kind() == std::io::ErrorKind::PermissionDenied {
|
.map(move |d| {
|
||||||
return ShellError::GenericError {
|
|
||||||
error: "Permission denied".into(),
|
|
||||||
msg: "The permissions may not allow access for this user".into(),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
error.into()
|
|
||||||
})?
|
|
||||||
.map(|d| {
|
|
||||||
d.map(|r| r.path())
|
d.map(|r| r.path())
|
||||||
.map_err(|e| ShellError::IOError { msg: e.to_string() })
|
.map_err(|err| IoError::new(err.kind(), span, f.clone()))
|
||||||
|
.map_err(ShellError::from)
|
||||||
});
|
});
|
||||||
if !use_threads {
|
if !use_threads {
|
||||||
let mut collected = items.collect::<Vec<_>>();
|
let mut collected = items.collect::<Vec<_>>();
|
||||||
|
@ -106,14 +106,10 @@ impl Command for Mktemp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let res = match uu_mktemp::mktemp(&options) {
|
let res = match uu_mktemp::mktemp(&options) {
|
||||||
Ok(res) => {
|
Ok(res) => res
|
||||||
res.into_os_string()
|
.into_os_string()
|
||||||
.into_string()
|
.into_string()
|
||||||
.map_err(|e| ShellError::IOErrorSpanned {
|
.map_err(|_| ShellError::NonUtf8 { span })?,
|
||||||
msg: e.to_string_lossy().to_string(),
|
|
||||||
span,
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: format!("{}", e),
|
error: format!("{}", e),
|
||||||
|
@ -7,7 +7,6 @@ mod open;
|
|||||||
mod rm;
|
mod rm;
|
||||||
mod save;
|
mod save;
|
||||||
mod start;
|
mod start;
|
||||||
mod touch;
|
|
||||||
mod ucp;
|
mod ucp;
|
||||||
mod umkdir;
|
mod umkdir;
|
||||||
mod umv;
|
mod umv;
|
||||||
@ -24,7 +23,6 @@ pub use mktemp::Mktemp;
|
|||||||
pub use rm::Rm;
|
pub use rm::Rm;
|
||||||
pub use save::Save;
|
pub use save::Save;
|
||||||
pub use start::Start;
|
pub use start::Start;
|
||||||
pub use touch::Touch;
|
|
||||||
pub use ucp::UCp;
|
pub use ucp::UCp;
|
||||||
pub use umkdir::UMkdir;
|
pub use umkdir::UMkdir;
|
||||||
pub use umv::UMv;
|
pub use umv::UMv;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
||||||
use nu_protocol::{ast, DataSource, NuGlob, PipelineMetadata};
|
use nu_protocol::{
|
||||||
use std::path::Path;
|
ast,
|
||||||
|
shell_error::{self, io::IoError},
|
||||||
|
DataSource, NuGlob, PipelineMetadata,
|
||||||
|
};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
use crate::database::SQLiteDatabase;
|
use crate::database::SQLiteDatabase;
|
||||||
@ -31,7 +35,13 @@ impl Command for Open {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("open")
|
Signature::build("open")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Any), (Type::String, Type::Any)])
|
.input_output_types(vec![
|
||||||
|
(Type::Nothing, Type::Any),
|
||||||
|
(Type::String, Type::Any),
|
||||||
|
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
||||||
|
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
|
||||||
|
(Type::Any, Type::Any),
|
||||||
|
])
|
||||||
.rest(
|
.rest(
|
||||||
"files",
|
"files",
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
|
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
|
||||||
@ -87,10 +97,10 @@ impl Command for Open {
|
|||||||
|
|
||||||
for path in nu_engine::glob_from(&path, &cwd, call_span, None)
|
for path in nu_engine::glob_from(&path, &cwd, call_span, None)
|
||||||
.map_err(|err| match err {
|
.map_err(|err| match err {
|
||||||
ShellError::DirectoryNotFound { span, .. } => ShellError::FileNotFound {
|
ShellError::Io(mut err) => {
|
||||||
file: path.item.to_string(),
|
err.span = arg_span;
|
||||||
span,
|
err.into()
|
||||||
},
|
}
|
||||||
_ => err,
|
_ => err,
|
||||||
})?
|
})?
|
||||||
.1
|
.1
|
||||||
@ -99,24 +109,29 @@ impl Command for Open {
|
|||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
|
|
||||||
if permission_denied(path) {
|
if permission_denied(path) {
|
||||||
|
let err = IoError::new(
|
||||||
|
std::io::ErrorKind::PermissionDenied,
|
||||||
|
arg_span,
|
||||||
|
PathBuf::from(path),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let error_msg = match path.metadata() {
|
let err = {
|
||||||
|
let mut err = err;
|
||||||
|
err.additional_context = Some(
|
||||||
|
match path.metadata() {
|
||||||
Ok(md) => format!(
|
Ok(md) => format!(
|
||||||
"The permissions of {:o} does not allow access for this user",
|
"The permissions of {:o} does not allow access for this user",
|
||||||
md.permissions().mode() & 0o0777
|
md.permissions().mode() & 0o0777
|
||||||
),
|
),
|
||||||
Err(e) => e.to_string(),
|
Err(e) => e.to_string(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
err
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
return Err(err.into());
|
||||||
let error_msg = String::from("Permission denied");
|
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: "Permission denied".into(),
|
|
||||||
msg: error_msg,
|
|
||||||
span: Some(arg_span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
if !raw {
|
if !raw {
|
||||||
@ -132,35 +147,25 @@ impl Command for Open {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = match std::fs::File::open(path) {
|
if path.is_dir() {
|
||||||
Ok(file) => file,
|
// At least under windows this check ensures that we don't get a
|
||||||
Err(err) => {
|
// permission denied error on directories
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::Io(IoError::new(
|
||||||
error: "Permission denied".into(),
|
shell_error::io::ErrorKind::IsADirectory,
|
||||||
msg: err.to_string(),
|
arg_span,
|
||||||
span: Some(arg_span),
|
PathBuf::from(path),
|
||||||
help: None,
|
)));
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Assigning content type should only happen in raw mode. Otherwise, the content
|
let file = std::fs::File::open(path)
|
||||||
// will potentially be in one of the built-in nushell `from xxx` formats and therefore
|
.map_err(|err| IoError::new(err.kind(), arg_span, PathBuf::from(path)))?;
|
||||||
// cease to be in the original content-type.... or so I'm told. :)
|
|
||||||
let content_type = if raw {
|
|
||||||
path.extension()
|
|
||||||
.map(|ext| ext.to_string_lossy().to_string())
|
|
||||||
.and_then(|ref s| detect_content_type(s))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// No content_type by default - Is added later if no converter is found
|
||||||
let stream = PipelineData::ByteStream(
|
let stream = PipelineData::ByteStream(
|
||||||
ByteStream::file(file, call_span, engine_state.signals().clone()),
|
ByteStream::file(file, call_span, engine_state.signals().clone()),
|
||||||
Some(PipelineMetadata {
|
Some(PipelineMetadata {
|
||||||
data_source: DataSource::FilePath(path.to_path_buf()),
|
data_source: DataSource::FilePath(path.to_path_buf()),
|
||||||
content_type,
|
content_type: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -203,7 +208,20 @@ impl Command for Open {
|
|||||||
}
|
}
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
None => output.push(stream),
|
None => {
|
||||||
|
// If no converter was found, add content-type metadata
|
||||||
|
let content_type = path
|
||||||
|
.extension()
|
||||||
|
.map(|ext| ext.to_string_lossy().to_string())
|
||||||
|
.and_then(|ref s| detect_content_type(s));
|
||||||
|
|
||||||
|
let stream_with_content_type =
|
||||||
|
stream.set_metadata(Some(PipelineMetadata {
|
||||||
|
data_source: DataSource::FilePath(path.to_path_buf()),
|
||||||
|
content_type,
|
||||||
|
}));
|
||||||
|
output.push(stream_with_content_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,11 @@ use super::util::try_interaction;
|
|||||||
use nu_engine::{command_prelude::*, env::current_dir};
|
use nu_engine::{command_prelude::*, env::current_dir};
|
||||||
use nu_glob::MatchOptions;
|
use nu_glob::MatchOptions;
|
||||||
use nu_path::expand_path_with;
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::{report_shell_error, NuGlob};
|
use nu_protocol::{
|
||||||
|
report_shell_error,
|
||||||
|
shell_error::{self, io::IoError},
|
||||||
|
NuGlob,
|
||||||
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::prelude::FileTypeExt;
|
use std::os::unix::prelude::FileTypeExt;
|
||||||
use std::{
|
use std::{
|
||||||
@ -299,9 +303,17 @@ fn rm(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// glob_from may canonicalize path and return `DirectoryNotFound`
|
// glob_from may canonicalize path and return an error when a directory is not found
|
||||||
// nushell should suppress the error if `--force` is used.
|
// nushell should suppress the error if `--force` is used.
|
||||||
if !(force && matches!(e, ShellError::DirectoryNotFound { .. })) {
|
if !(force
|
||||||
|
&& matches!(
|
||||||
|
e,
|
||||||
|
ShellError::Io(IoError {
|
||||||
|
kind: shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotFound),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
))
|
||||||
|
{
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,8 +425,7 @@ fn rm(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
let msg = format!("Could not delete {:}: {e:}", f.to_string_lossy());
|
Err(ShellError::Io(IoError::new(e.kind(), span, f)))
|
||||||
Err(ShellError::RemoveNotPossible { msg, span })
|
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
let msg = if interactive && !confirmed {
|
let msg = if interactive && !confirmed {
|
||||||
"not deleted"
|
"not deleted"
|
||||||
|
@ -4,8 +4,8 @@ use nu_engine::get_eval_block;
|
|||||||
use nu_engine::{command_prelude::*, current_dir};
|
use nu_engine::{command_prelude::*, current_dir};
|
||||||
use nu_path::expand_path_with;
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast, byte_stream::copy_with_signals, process::ChildPipe, ByteStreamSource, DataSource, OutDest,
|
ast, byte_stream::copy_with_signals, process::ChildPipe, shell_error::io::IoError,
|
||||||
PipelineMetadata, Signals,
|
ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -86,6 +86,7 @@ impl Command for Save {
|
|||||||
span: arg.span,
|
span: arg.span,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let from_io_error = IoError::factory(span, path.item.as_path());
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ByteStream(stream, metadata) => {
|
PipelineData::ByteStream(stream, metadata) => {
|
||||||
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
||||||
@ -129,7 +130,7 @@ impl Command for Save {
|
|||||||
io::copy(&mut tee, &mut io::stderr())
|
io::copy(&mut tee, &mut io::stderr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.err_span(span)?;
|
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@ impl Command for Save {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.err_span(span)?;
|
.map_err(&from_io_error)?;
|
||||||
|
|
||||||
let res = match stdout {
|
let res = match stdout {
|
||||||
ChildPipe::Pipe(pipe) => {
|
ChildPipe::Pipe(pipe) => {
|
||||||
@ -203,15 +204,10 @@ impl Command for Save {
|
|||||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||||
for val in ls {
|
for val in ls {
|
||||||
file.write_all(&value_to_bytes(val)?)
|
file.write_all(&value_to_bytes(val)?)
|
||||||
.map_err(|err| ShellError::IOError {
|
.map_err(&from_io_error)?;
|
||||||
msg: err.to_string(),
|
file.write_all("\n".as_bytes()).map_err(&from_io_error)?;
|
||||||
})?;
|
|
||||||
file.write_all("\n".as_bytes())
|
|
||||||
.map_err(|err| ShellError::IOError {
|
|
||||||
msg: err.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
file.flush()?;
|
file.flush().map_err(&from_io_error)?;
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
@ -232,11 +228,8 @@ impl Command for Save {
|
|||||||
// Only open file after successful conversion
|
// Only open file after successful conversion
|
||||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||||
|
|
||||||
file.write_all(&bytes).map_err(|err| ShellError::IOError {
|
file.write_all(&bytes).map_err(&from_io_error)?;
|
||||||
msg: err.to_string(),
|
file.flush().map_err(&from_io_error)?;
|
||||||
})?;
|
|
||||||
|
|
||||||
file.flush()?;
|
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
@ -420,18 +413,27 @@ fn prepare_path(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
|
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
|
||||||
let file = match (append, path.exists()) {
|
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
|
||||||
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
|
{
|
||||||
_ => std::fs::File::create(path),
|
(true, true) => std::fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(path)
|
||||||
|
.map_err(|err| err.kind().into()),
|
||||||
|
_ => {
|
||||||
|
// This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893)
|
||||||
|
// A TOCTOU problem exists here, which may cause wrong error message to be shown
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
if path.is_dir() {
|
||||||
|
Err(nu_protocol::shell_error::io::ErrorKind::IsADirectory)
|
||||||
|
} else {
|
||||||
|
std::fs::File::create(path).map_err(|err| err.kind().into())
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
std::fs::File::create(path).map_err(|err| err.kind().into())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
file.map_err(|e| ShellError::GenericError {
|
file.map_err(|err_kind| ShellError::Io(IoError::new(err_kind, span, PathBuf::from(path))))
|
||||||
error: format!("Problem with [{}], Permission denied", path.display()),
|
|
||||||
msg: e.to_string(),
|
|
||||||
span: Some(span),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get output file and optional stderr file
|
/// Get output file and optional stderr file
|
||||||
@ -478,6 +480,9 @@ fn stream_to_file(
|
|||||||
span: Span,
|
span: Span,
|
||||||
progress: bool,
|
progress: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
// TODO: maybe we can get a path in here
|
||||||
|
let from_io_error = IoError::factory(span, None);
|
||||||
|
|
||||||
// https://github.com/nushell/nushell/pull/9377 contains the reason for not using `BufWriter`
|
// https://github.com/nushell/nushell/pull/9377 contains the reason for not using `BufWriter`
|
||||||
if progress {
|
if progress {
|
||||||
let mut bytes_processed = 0;
|
let mut bytes_processed = 0;
|
||||||
@ -497,7 +502,7 @@ fn stream_to_file(
|
|||||||
match reader.fill_buf() {
|
match reader.fill_buf() {
|
||||||
Ok(&[]) => break Ok(()),
|
Ok(&[]) => break Ok(()),
|
||||||
Ok(buf) => {
|
Ok(buf) => {
|
||||||
file.write_all(buf).err_span(span)?;
|
file.write_all(buf).map_err(&from_io_error)?;
|
||||||
let len = buf.len();
|
let len = buf.len();
|
||||||
reader.consume(len);
|
reader.consume(len);
|
||||||
bytes_processed += len as u64;
|
bytes_processed += len as u64;
|
||||||
@ -515,9 +520,9 @@ fn stream_to_file(
|
|||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
let _ = file.flush();
|
let _ = file.flush();
|
||||||
bar.abandoned_msg("# Error while saving #".to_owned());
|
bar.abandoned_msg("# Error while saving #".to_owned());
|
||||||
Err(err.into_spanned(span).into())
|
Err(from_io_error(err).into())
|
||||||
} else {
|
} else {
|
||||||
file.flush().err_span(span)?;
|
file.flush().map_err(&from_io_error)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nu_engine::{command_prelude::*, env_to_strings};
|
use nu_engine::{command_prelude::*, env_to_strings};
|
||||||
use nu_path::canonicalize_with;
|
use nu_protocol::ShellError;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
path::Path,
|
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ impl Command for Start {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Open a folder, file or website in the default application or viewer."
|
"Open a folder, file, or website in the default application or viewer."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -26,7 +25,7 @@ impl Command for Start {
|
|||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("start")
|
Signature::build("start")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||||
.required("path", SyntaxShape::String, "Path to open.")
|
.required("path", SyntaxShape::String, "Path or URL to open.")
|
||||||
.category(Category::FileSystem)
|
.category(Category::FileSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,59 +41,29 @@ impl Command for Start {
|
|||||||
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
||||||
span: path.span,
|
span: path.span,
|
||||||
};
|
};
|
||||||
let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
let path_no_whitespace = path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
||||||
// only check if file exists in current current directory
|
// Attempt to parse the input as a URL
|
||||||
let file_path = Path::new(path_no_whitespace);
|
if let Ok(url) = url::Url::parse(path_no_whitespace) {
|
||||||
if file_path.exists() {
|
|
||||||
open_path(path_no_whitespace, engine_state, stack, path.span)?;
|
|
||||||
} else if file_path.starts_with("https://") || file_path.starts_with("http://") {
|
|
||||||
let url = url::Url::parse(&path.item).map_err(|_| ShellError::GenericError {
|
|
||||||
error: format!("Cannot parse url: {}", &path.item),
|
|
||||||
msg: "".to_string(),
|
|
||||||
span: Some(path.span),
|
|
||||||
help: Some("cannot parse".to_string()),
|
|
||||||
inner: vec![],
|
|
||||||
})?;
|
|
||||||
open_path(url.as_str(), engine_state, stack, path.span)?;
|
open_path(url.as_str(), engine_state, stack, path.span)?;
|
||||||
} else {
|
return Ok(PipelineData::Empty);
|
||||||
// try to distinguish between file not found and opening url without prefix
|
}
|
||||||
|
// If it's not a URL, treat it as a file path
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
let cwd = engine_state.cwd(Some(stack))?;
|
||||||
if let Ok(canon_path) = canonicalize_with(path_no_whitespace, cwd) {
|
let full_path = cwd.join(path_no_whitespace);
|
||||||
open_path(canon_path, engine_state, stack, path.span)?;
|
// Check if the path exists or if it's a valid file/directory
|
||||||
} else {
|
if full_path.exists() {
|
||||||
// open crate does not allow opening URL without prefix
|
open_path(full_path, engine_state, stack, path.span)?;
|
||||||
let path_with_prefix = Path::new("https://").join(&path.item);
|
return Ok(PipelineData::Empty);
|
||||||
let common_domains = ["com", "net", "org", "edu", "sh"];
|
}
|
||||||
if let Some(url) = path_with_prefix.to_str() {
|
// If neither file nor URL, return an error
|
||||||
let url = url::Url::parse(url).map_err(|_| ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: format!("Cannot parse url: {}", &path.item),
|
error: format!("Cannot find file or URL: {}", &path.item),
|
||||||
msg: "".into(),
|
msg: "".into(),
|
||||||
span: Some(path.span),
|
span: Some(path.span),
|
||||||
help: Some("cannot parse".into()),
|
help: Some("Ensure the path or URL is correct and try again.".into()),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})?;
|
})
|
||||||
if let Some(domain) = url.host() {
|
|
||||||
let domain = domain.to_string();
|
|
||||||
let ext = Path::new(&domain).extension().and_then(|s| s.to_str());
|
|
||||||
if let Some(url_ext) = ext {
|
|
||||||
if common_domains.contains(&url_ext) {
|
|
||||||
open_path(url.as_str(), engine_state, stack, path.span)?;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: format!("Cannot find file or url: {}", &path.item),
|
|
||||||
msg: "".into(),
|
|
||||||
span: Some(path.span),
|
|
||||||
help: Some("Use prefix https:// to disambiguate URLs from files".into()),
|
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(PipelineData::Empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<nu_protocol::Example> {
|
fn examples(&self) -> Vec<nu_protocol::Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
@ -113,15 +82,20 @@ impl Command for Start {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Open a pdf with the default pdf viewer",
|
description: "Open a PDF with the default PDF viewer",
|
||||||
example: "start file.pdf",
|
example: "start file.pdf",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Open a website with default browser",
|
description: "Open a website with the default browser",
|
||||||
example: "start https://www.nushell.sh",
|
example: "start https://www.nushell.sh",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Open an application-registered protocol URL",
|
||||||
|
example: "start obsidian://open?vault=Test",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,47 +116,48 @@ fn try_commands(
|
|||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let env_vars_str = env_to_strings(engine_state, stack)?;
|
let env_vars_str = env_to_strings(engine_state, stack)?;
|
||||||
let cmd_run_result = commands.into_iter().map(|mut cmd| {
|
let mut last_err = None;
|
||||||
|
|
||||||
|
for mut cmd in commands {
|
||||||
let status = cmd
|
let status = cmd
|
||||||
.envs(&env_vars_str)
|
.envs(&env_vars_str)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Ok(status) if status.success() => Ok(()),
|
Ok(status) if status.success() => return Ok(()),
|
||||||
Ok(status) => Err(format!(
|
Ok(status) => {
|
||||||
"\nCommand `{}` failed with {}",
|
last_err = Some(format!(
|
||||||
|
"Command `{}` failed with exit code: {}",
|
||||||
format_command(&cmd),
|
format_command(&cmd),
|
||||||
status
|
status.code().unwrap_or(-1)
|
||||||
)),
|
));
|
||||||
Err(err) => Err(format!(
|
}
|
||||||
"\nCommand `{}` failed with {}",
|
Err(err) => {
|
||||||
|
last_err = Some(format!(
|
||||||
|
"Command `{}` failed with error: {}",
|
||||||
format_command(&cmd),
|
format_command(&cmd),
|
||||||
err
|
err
|
||||||
)),
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
for one_result in cmd_run_result {
|
Err(ShellError::ExternalCommand {
|
||||||
if let Err(err_msg) = one_result {
|
label: "Failed to start the specified path or URL".to_string(),
|
||||||
return Err(ShellError::ExternalCommand {
|
help: format!(
|
||||||
label: "No command found to start with this path".to_string(),
|
"Try a different path or install the appropriate application.\n{}",
|
||||||
help: "Try different path or install appropriate command\n".to_string() + &err_msg,
|
last_err.unwrap_or_default()
|
||||||
|
),
|
||||||
span,
|
span,
|
||||||
});
|
})
|
||||||
} else if one_result.is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_command(command: &std::process::Command) -> String {
|
fn format_command(command: &std::process::Command) -> String {
|
||||||
let parts_iter = std::iter::repeat(command.get_program())
|
let parts_iter = std::iter::once(command.get_program()).chain(command.get_args());
|
||||||
.take(1)
|
Itertools::intersperse(parts_iter, OsStr::new(" "))
|
||||||
.chain(command.get_args());
|
|
||||||
Itertools::intersperse(parts_iter, " ".as_ref())
|
|
||||||
.collect::<OsString>()
|
.collect::<OsString>()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned()
|
.into_owned()
|
||||||
|
@ -1,249 +0,0 @@
|
|||||||
use filetime::FileTime;
|
|
||||||
use nu_engine::command_prelude::*;
|
|
||||||
use nu_path::expand_path_with;
|
|
||||||
use nu_protocol::NuGlob;
|
|
||||||
use std::{fs::OpenOptions, time::SystemTime};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Touch;
|
|
||||||
|
|
||||||
impl Command for Touch {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"touch"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["create", "file"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("touch")
|
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
||||||
.rest(
|
|
||||||
"files",
|
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Filepath]),
|
|
||||||
"The file(s) to create."
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"reference",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"change the file or directory time to the time of the reference file/directory",
|
|
||||||
Some('r'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"modified",
|
|
||||||
"change the modification time of the file or directory. If no reference file/directory is given, the current time is used",
|
|
||||||
Some('m'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"access",
|
|
||||||
"change the access time of the file or directory. If no reference file/directory is given, the current time is used",
|
|
||||||
Some('a'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"no-create",
|
|
||||||
"do not create the file if it does not exist",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"no-deref",
|
|
||||||
"do not follow symlinks",
|
|
||||||
Some('s')
|
|
||||||
)
|
|
||||||
.category(Category::FileSystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Creates one or more files."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let mut change_mtime: bool = call.has_flag(engine_state, stack, "modified")?;
|
|
||||||
let mut change_atime: bool = call.has_flag(engine_state, stack, "access")?;
|
|
||||||
let no_follow_symlinks: bool = call.has_flag(engine_state, stack, "no-deref")?;
|
|
||||||
let reference: Option<Spanned<String>> = call.get_flag(engine_state, stack, "reference")?;
|
|
||||||
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
|
|
||||||
let files = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
|
||||||
|
|
||||||
if files.is_empty() {
|
|
||||||
return Err(ShellError::MissingParameter {
|
|
||||||
param_name: "requires file paths".to_string(),
|
|
||||||
span: call.head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mtime = SystemTime::now();
|
|
||||||
let mut atime = mtime;
|
|
||||||
|
|
||||||
// Change both times if neither is specified
|
|
||||||
if !change_mtime && !change_atime {
|
|
||||||
change_mtime = true;
|
|
||||||
change_atime = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(reference) = reference {
|
|
||||||
let reference_path = nu_path::expand_path_with(reference.item, &cwd, true);
|
|
||||||
let exists = if no_follow_symlinks {
|
|
||||||
// There's no symlink_exists function, so we settle for
|
|
||||||
// getting direct metadata and if it's OK, it exists
|
|
||||||
reference_path.symlink_metadata().is_ok()
|
|
||||||
} else {
|
|
||||||
reference_path.exists()
|
|
||||||
};
|
|
||||||
if !exists {
|
|
||||||
return Err(ShellError::FileNotFoundCustom {
|
|
||||||
msg: "Reference path not found".into(),
|
|
||||||
span: reference.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = if no_follow_symlinks {
|
|
||||||
reference_path.symlink_metadata()
|
|
||||||
} else {
|
|
||||||
reference_path.metadata()
|
|
||||||
};
|
|
||||||
let metadata = metadata.map_err(|err| ShellError::IOErrorSpanned {
|
|
||||||
msg: format!("Failed to read metadata: {err}"),
|
|
||||||
span: reference.span,
|
|
||||||
})?;
|
|
||||||
mtime = metadata
|
|
||||||
.modified()
|
|
||||||
.map_err(|err| ShellError::IOErrorSpanned {
|
|
||||||
msg: format!("Failed to read modified time: {err}"),
|
|
||||||
span: reference.span,
|
|
||||||
})?;
|
|
||||||
atime = metadata
|
|
||||||
.accessed()
|
|
||||||
.map_err(|err| ShellError::IOErrorSpanned {
|
|
||||||
msg: format!("Failed to read access time: {err}"),
|
|
||||||
span: reference.span,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for glob in files {
|
|
||||||
let path = expand_path_with(glob.item.as_ref(), &cwd, glob.item.is_expand());
|
|
||||||
let exists = if no_follow_symlinks {
|
|
||||||
path.symlink_metadata().is_ok()
|
|
||||||
} else {
|
|
||||||
path.exists()
|
|
||||||
};
|
|
||||||
|
|
||||||
// If --no-create is passed and the file/dir does not exist there's nothing to do
|
|
||||||
if no_create && !exists {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If --no-deref was passed in, the behavior of touch is to error on missing
|
|
||||||
if no_follow_symlinks && !exists {
|
|
||||||
return Err(ShellError::FileNotFound {
|
|
||||||
file: path.to_string_lossy().into_owned(),
|
|
||||||
span: glob.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a file at the given path unless the path is a directory (or a symlink with -d)
|
|
||||||
if !path.is_dir() && (!no_follow_symlinks || !path.is_symlink()) {
|
|
||||||
if let Err(err) = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(false)
|
|
||||||
.open(&path)
|
|
||||||
{
|
|
||||||
return Err(ShellError::CreateNotPossible {
|
|
||||||
msg: format!("Failed to create file: {err}"),
|
|
||||||
span: glob.span,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to inefficiently access the target metadata to not reset it
|
|
||||||
// in set_symlink_file_times, because the filetime doesn't expose individual methods for it
|
|
||||||
let get_target_metadata = || {
|
|
||||||
path.symlink_metadata()
|
|
||||||
.map_err(|err| ShellError::IOErrorSpanned {
|
|
||||||
msg: format!("Failed to read metadata: {err}"),
|
|
||||||
span: glob.span,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if change_mtime {
|
|
||||||
let result = if no_follow_symlinks {
|
|
||||||
filetime::set_symlink_file_times(
|
|
||||||
&path,
|
|
||||||
if change_atime {
|
|
||||||
FileTime::from_system_time(atime)
|
|
||||||
} else {
|
|
||||||
FileTime::from_system_time(get_target_metadata()?.accessed()?)
|
|
||||||
},
|
|
||||||
FileTime::from_system_time(mtime),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
filetime::set_file_mtime(&path, FileTime::from_system_time(mtime))
|
|
||||||
};
|
|
||||||
if let Err(err) = result {
|
|
||||||
return Err(ShellError::ChangeModifiedTimeNotPossible {
|
|
||||||
msg: format!("Failed to change the modified time: {err}"),
|
|
||||||
span: glob.span,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if change_atime {
|
|
||||||
let result = if no_follow_symlinks {
|
|
||||||
filetime::set_symlink_file_times(
|
|
||||||
&path,
|
|
||||||
FileTime::from_system_time(atime),
|
|
||||||
if change_mtime {
|
|
||||||
FileTime::from_system_time(mtime)
|
|
||||||
} else {
|
|
||||||
FileTime::from_system_time(get_target_metadata()?.modified()?)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
filetime::set_file_atime(&path, FileTime::from_system_time(atime))
|
|
||||||
};
|
|
||||||
if let Err(err) = result {
|
|
||||||
return Err(ShellError::ChangeAccessTimeNotPossible {
|
|
||||||
msg: format!("Failed to change the access time: {err}"),
|
|
||||||
span: glob.span,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Creates \"fixture.json\"",
|
|
||||||
example: "touch fixture.json",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Creates files a, b and c",
|
|
||||||
example: "touch a b c",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: r#"Changes the last modified time of "fixture.json" to today's date"#,
|
|
||||||
example: "touch -m fixture.json",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: r#"Changes the last modified time of file d and e to "fixture.json"'s last modified time"#,
|
|
||||||
example: r#"touch -m -r fixture.json d e"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{command_prelude::*, current_dir};
|
use nu_engine::{command_prelude::*, current_dir};
|
||||||
use nu_protocol::NuGlob;
|
use nu_protocol::{shell_error::io::IoError, NuGlob};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
||||||
|
|
||||||
@ -142,19 +142,9 @@ impl Command for UCp {
|
|||||||
} else {
|
} else {
|
||||||
uu_cp::OverwriteMode::Clobber(uu_cp::ClobberMode::Standard)
|
uu_cp::OverwriteMode::Clobber(uu_cp::ClobberMode::Standard)
|
||||||
};
|
};
|
||||||
#[cfg(any(
|
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
|
||||||
target_os = "linux",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "android",
|
|
||||||
target_os = "macos"
|
|
||||||
))]
|
|
||||||
let reflink_mode = uu_cp::ReflinkMode::Auto;
|
let reflink_mode = uu_cp::ReflinkMode::Auto;
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
|
||||||
target_os = "linux",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "android",
|
|
||||||
target_os = "macos"
|
|
||||||
)))]
|
|
||||||
let reflink_mode = uu_cp::ReflinkMode::Never;
|
let reflink_mode = uu_cp::ReflinkMode::Never;
|
||||||
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
@ -207,10 +197,11 @@ impl Command for UCp {
|
|||||||
.map(|f| f.1)?
|
.map(|f| f.1)?
|
||||||
.collect();
|
.collect();
|
||||||
if exp_files.is_empty() {
|
if exp_files.is_empty() {
|
||||||
return Err(ShellError::FileNotFound {
|
return Err(ShellError::Io(IoError::new(
|
||||||
file: p.item.to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: p.span,
|
p.span,
|
||||||
});
|
PathBuf::from(p.item.to_string()),
|
||||||
|
)));
|
||||||
};
|
};
|
||||||
let mut app_vals: Vec<PathBuf> = Vec::new();
|
let mut app_vals: Vec<PathBuf> = Vec::new();
|
||||||
for v in exp_files {
|
for v in exp_files {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{command_prelude::*, current_dir};
|
use nu_engine::{command_prelude::*, current_dir};
|
||||||
use nu_path::expand_path_with;
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::NuGlob;
|
use nu_protocol::{shell_error::io::IoError, NuGlob};
|
||||||
use std::{ffi::OsString, path::PathBuf};
|
use std::{ffi::OsString, path::PathBuf};
|
||||||
use uu_mv::{BackupMode, UpdateMode};
|
use uu_mv::{BackupMode, UpdateMode};
|
||||||
|
|
||||||
@ -138,10 +138,11 @@ impl Command for UMv {
|
|||||||
.map(|f| f.1)?
|
.map(|f| f.1)?
|
||||||
.collect();
|
.collect();
|
||||||
if exp_files.is_empty() {
|
if exp_files.is_empty() {
|
||||||
return Err(ShellError::FileNotFound {
|
return Err(ShellError::Io(IoError::new(
|
||||||
file: p.item.to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: p.span,
|
p.span,
|
||||||
});
|
PathBuf::from(p.item.to_string()),
|
||||||
|
)));
|
||||||
};
|
};
|
||||||
let mut app_vals: Vec<PathBuf> = Vec::new();
|
let mut app_vals: Vec<PathBuf> = Vec::new();
|
||||||
for v in exp_files {
|
for v in exp_files {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user