mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
109 Commits
Author | SHA1 | Date | |
---|---|---|---|
91ff57faa7 | |||
b99affba4b | |||
639bd4fc2e | |||
a0f38f8845 | |||
a11c9e9d70 | |||
bdbcf82967 | |||
1f47d72e86 | |||
d83781ddec | |||
e32e55938b | |||
de08b68ba8 | |||
0e3a8c552c | |||
389e7d2502 | |||
fce6146576 | |||
02313e6819 | |||
df0a174802 | |||
bcb7ef48b6 | |||
9f714e62cb | |||
a95c2198a6 | |||
2df91e7f92 | |||
44be445b57 | |||
e43632fd95 | |||
69e4abad0f | |||
3bedbd0669 | |||
1d15bbc95b | |||
5002d87af4 | |||
2a3805c164 | |||
52f646d8db | |||
36c1073441 | |||
2979595cc5 | |||
d67120be19 | |||
ad31f1cf26 | |||
99798ace7d | |||
ba4becc61c | |||
397499b106 | |||
55c3fc9141 | |||
2830ec008c | |||
4c8b09eb97 | |||
98e0864be8 | |||
6dc71f5ad0 | |||
d6f4e4c4fe | |||
33ae71f300 | |||
abcca0897e | |||
b1379b2b14 | |||
27ebccce80 | |||
6964968f14 | |||
68377c176d | |||
baadaee016 | |||
199aa2ad3a | |||
29b176b719 | |||
6ce20675eb | |||
1e9967c3bf | |||
e0bc85d0dd | |||
e3fd4d3f81 | |||
00709fc5bd | |||
157494e803 | |||
f03ba6793e | |||
475aa4f1dd | |||
1d6ac16530 | |||
573a7e2c7b | |||
cebbc82322 | |||
cf5b2aeb88 | |||
52eb9c2ef3 | |||
702dcd8581 | |||
9e6ada6411 | |||
a38663ec90 | |||
02804ab537 | |||
b2d0d9cf13 | |||
46589faaca | |||
166d5fa4ff | |||
4bd38847c2 | |||
30a4187be4 | |||
f0c83a4459 | |||
fc61416c79 | |||
8200831b07 | |||
497954d84c | |||
bcaef8959c | |||
5bef81a059 | |||
d68c3ec89a | |||
0c72f881a6 | |||
8195e2d638 | |||
e8c20390e0 | |||
13df0af514 | |||
54e9aa92bc | |||
1afff777a6 | |||
071faae772 | |||
08a241f763 | |||
63f9e273b3 | |||
71d604067a | |||
66d0e18674 | |||
a940a8aa80 | |||
0d30550950 | |||
65bb0ff167 | |||
151767a5e3 | |||
a948ec6c2c | |||
28a7461057 | |||
6f47990a63 | |||
183c2221bb | |||
03ee54a4df | |||
2541a712e4 | |||
ee877607fb | |||
93351b889a | |||
5fa9d76500 | |||
cd0d0364ec | |||
cf5fec63c0 | |||
5c5cf418fb | |||
fb14008f50 | |||
18c8c16c5e | |||
299a218de7 | |||
1a081c09de |
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
@ -20,6 +20,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: rustsec/audit-check@v1.4.1
|
||||
- uses: rustsec/audit-check@v2.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
@ -64,7 +64,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_*
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
|
||||
- name: Install Nushell
|
||||
run: cargo install --path . --locked --no-default-features
|
||||
@ -144,7 +144,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||
|
25
.github/workflows/nightly-build.yml
vendored
25
.github/workflows/nightly-build.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
# if: github.repository == 'nushell/nightly'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
ref: main
|
||||
@ -36,10 +36,10 @@ jobs:
|
||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.97.1
|
||||
version: 0.98.0
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
@ -65,7 +65,7 @@ jobs:
|
||||
}
|
||||
|
||||
standard:
|
||||
name: Std
|
||||
name: Nu
|
||||
needs: prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -82,6 +82,7 @@ jobs:
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
extra: ['bin']
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
@ -114,11 +115,13 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
@ -128,15 +131,15 @@ jobs:
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.97.1
|
||||
version: 0.98.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -187,14 +190,14 @@ jobs:
|
||||
- name: Waiting for Release
|
||||
run: sleep 1800
|
||||
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.97.1
|
||||
version: 0.98.0
|
||||
|
||||
# Keep the last a few releases
|
||||
- name: Delete Older Releases
|
||||
|
7
.github/workflows/release-pkg.nu
vendored
7
.github/workflows/release-pkg.nu
vendored
@ -98,6 +98,13 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER = 'armv7r-linux-musleabihf-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
'loongarch64-unknown-linux-gnu' => {
|
||||
aria2c https://github.com/loongson/build-tools/releases/download/2024.08.08/x86_64-cross-tools-loongarch64-binutils_2.43-gcc_14.2.0-glibc_2.40.tar.xz
|
||||
tar xf x86_64-cross-tools-loongarch64-*.tar.xz
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.PWD)/cross-tools/bin')
|
||||
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
|
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@ -14,8 +14,8 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
standard:
|
||||
name: Std
|
||||
release:
|
||||
name: Nu
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -32,6 +32,7 @@ jobs:
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
extra: ['bin']
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
@ -64,27 +65,29 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update Rust Toolchain Target
|
||||
run: |
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.97.1
|
||||
version: 0.98.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -104,3 +107,26 @@ jobs:
|
||||
files: ${{ steps.nu.outputs.archive }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
sha256sum:
|
||||
needs: release
|
||||
name: Create Sha256sum
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Release Archives
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: >-
|
||||
gh release download ${{ github.ref_name }}
|
||||
--repo ${{ github.repository }}
|
||||
--pattern '*'
|
||||
--dir release
|
||||
- name: Create Checksums
|
||||
run: cd release && shasum -a 256 * > ../SHA256SUMS
|
||||
- name: Publish Checksums
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
draft: true
|
||||
files: SHA256SUMS
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.24.5
|
||||
uses: crate-ci/typos@v1.26.0
|
||||
|
128
Cargo.lock
generated
128
Cargo.lock
generated
@ -1479,9 +1479,9 @@ checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
@ -1865,6 +1865,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
@ -1979,9 +1985,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human-date-parser"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5cbf96a7157cc349eeafe4595e4f283c3fcab73b5a656d8b2cc00a870a74e1a"
|
||||
checksum = "1116cf4debfe770c12168458321c4a8591b71c4c19f7100de07c84cf81701c63"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"pest",
|
||||
@ -2074,12 +2080,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown 0.15.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -2938,7 +2944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm 0.28.1",
|
||||
@ -2993,7 +2999,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm 0.28.1",
|
||||
@ -3028,7 +3034,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
@ -3040,7 +3046,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck",
|
||||
@ -3065,7 +3071,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"nu-engine",
|
||||
@ -3077,7 +3083,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-plugin"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"nu-engine",
|
||||
@ -3088,7 +3094,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
@ -3100,7 +3106,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
@ -3210,7 +3216,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-derive-value"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
@ -3221,7 +3227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-glob",
|
||||
@ -3233,7 +3239,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"anyhow",
|
||||
@ -3245,6 +3251,7 @@ dependencies = [
|
||||
"nu-engine",
|
||||
"nu-json",
|
||||
"nu-parser",
|
||||
"nu-path",
|
||||
"nu-pretty-hex",
|
||||
"nu-protocol",
|
||||
"nu-table",
|
||||
@ -3258,14 +3265,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"linked-hash-map",
|
||||
@ -3278,7 +3285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
@ -3299,7 +3306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
@ -3315,7 +3322,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"omnipath",
|
||||
@ -3324,7 +3331,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix 0.29.0",
|
||||
@ -3340,7 +3347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-core"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"log",
|
||||
@ -3354,7 +3361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-engine",
|
||||
@ -3370,7 +3377,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-protocol",
|
||||
"nu-utils",
|
||||
@ -3382,7 +3389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-lang",
|
||||
@ -3400,7 +3407,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
@ -3409,7 +3416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"byte-unit",
|
||||
@ -3447,7 +3454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
@ -3458,7 +3465,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.13.0",
|
||||
@ -3476,7 +3483,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
@ -3486,11 +3493,12 @@ dependencies = [
|
||||
"nu-utils",
|
||||
"once_cell",
|
||||
"tabled",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
@ -3498,7 +3506,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
@ -3510,7 +3518,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
@ -3536,7 +3544,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
@ -3546,7 +3554,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"eml-parser",
|
||||
@ -3561,7 +3569,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
@ -3570,7 +3578,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
@ -3579,12 +3587,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_polars"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz 0.9.0",
|
||||
"env_logger 0.11.5",
|
||||
"fancy-regex",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap",
|
||||
"log",
|
||||
"mimalloc",
|
||||
@ -3613,7 +3622,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
@ -3628,7 +3637,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"serde",
|
||||
@ -3742,7 +3751,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nuon"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
@ -3877,9 +3886,12 @@ checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
@ -5135,9 +5147,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.35.0"
|
||||
version = "0.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5289de810296f8f2ff58d35544d92ae98d0a631453388bc3e608086be0fa596"
|
||||
checksum = "57016fc0ec4c651a05cb80fb1200b7c3518afcc2f769edffe15aca2363395c15"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
@ -5433,9 +5445,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
version = "0.38.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
@ -5715,9 +5727,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe0bac8a8752586a618a1c80d01d8ca5d40fce4f6077fbc851e48dcbdb90df"
|
||||
checksum = "fca0e9bdc073d7173ba993fb7886477af5df75588b57afcb4b96f21911ab0bfa"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
@ -6120,9 +6132,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tango-bench"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e79d9e491b718fb52bb2052726a0e66fe89a31cc6979fe58c72c73f058c279e"
|
||||
checksum = "257822358c6f206fed78bfe6369cf959063b0644d70f88df6b19f2dadc93423e"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anyhow",
|
||||
@ -6153,9 +6165,9 @@ checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.12.0"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
@ -6568,9 +6580,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
|
56
Cargo.toml
56
Cargo.toml
@ -11,7 +11,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.79.0"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -91,8 +91,8 @@ filesize = "0.2"
|
||||
filetime = "0.2"
|
||||
fuzzy-matcher = "0.3"
|
||||
heck = "0.5.0"
|
||||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.5"
|
||||
human-date-parser = "0.2.0"
|
||||
indexmap = "2.6"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.2.0"
|
||||
is_executable = "1.0"
|
||||
@ -118,7 +118,7 @@ nu-ansi-term = "0.50.1"
|
||||
num-format = "0.4"
|
||||
num-traits = "0.2"
|
||||
omnipath = "0.1"
|
||||
once_cell = "1.18"
|
||||
once_cell = "1.20"
|
||||
open = "5.3"
|
||||
os_pipe = { version = "1.2", features = ["io_safety"] }
|
||||
pathdiff = "0.2"
|
||||
@ -137,7 +137,7 @@ rand = "0.8"
|
||||
rand_chacha = "0.3.1"
|
||||
ratatui = "0.26"
|
||||
rayon = "1.10"
|
||||
reedline = "0.35.0"
|
||||
reedline = "0.36.0"
|
||||
regex = "1.9.5"
|
||||
rmp = "0.8"
|
||||
rmp-serde = "1.3"
|
||||
@ -155,13 +155,13 @@ strip-ansi-escapes = "0.2.0"
|
||||
syn = "2.0"
|
||||
sysinfo = "0.30"
|
||||
tabled = { version = "0.16.0", default-features = false }
|
||||
tempfile = "3.10"
|
||||
tempfile = "3.13"
|
||||
terminal_size = "0.3"
|
||||
titlecase = "2.0"
|
||||
toml = "0.8"
|
||||
trash = "3.3"
|
||||
umask = "2.1"
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-width = "0.1"
|
||||
ureq = { version = "2.10", default-features = false }
|
||||
url = "2.2"
|
||||
@ -189,22 +189,22 @@ unchecked_duration_subtraction = "warn"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.98.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.98.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.98.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.98.0", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.98.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.98.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.98.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.98.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.98.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.98.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.98.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.98.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.98.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.98.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.98.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.98.0" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.99.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.99.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.99.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.99.0", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.99.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.99.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.99.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.99.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.99.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.99.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.99.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.99.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.99.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.99.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.99.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.99.0" }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
@ -234,12 +234,12 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.98.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.98.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.98.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.99.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.99.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.99.0" }
|
||||
assert_cmd = "2.0"
|
||||
dirs = { workspace = true }
|
||||
tango-bench = "0.5"
|
||||
tango-bench = "0.6"
|
||||
pretty_assertions = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
@ -320,4 +320,4 @@ bench = false
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
harness = false
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.98.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.99.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.99.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.99.0" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.98.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.98.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.99.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.99.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.99.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.99.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.99.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.99.0" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -42,91 +42,75 @@ impl Command for History {
|
||||
let Some(history) = engine_state.history_config() else {
|
||||
return Ok(PipelineData::empty());
|
||||
};
|
||||
|
||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
||||
if let Some(config_path) = nu_path::config_dir() {
|
||||
let clear = call.has_flag(engine_state, stack, "clear")?;
|
||||
let long = call.has_flag(engine_state, stack, "long")?;
|
||||
let signals = engine_state.signals().clone();
|
||||
let Some(history_path) = history.file_path() else {
|
||||
return Err(ShellError::ConfigDirNotFound { span: Some(head) });
|
||||
};
|
||||
|
||||
let mut history_path = config_path;
|
||||
history_path.push("nushell");
|
||||
match history.file_format {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
history_path.push("history.sqlite3");
|
||||
}
|
||||
HistoryFileFormat::Plaintext => {
|
||||
history_path.push("history.txt");
|
||||
}
|
||||
}
|
||||
if call.has_flag(engine_state, stack, "clear")? {
|
||||
let _ = std::fs::remove_file(history_path);
|
||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
||||
return Ok(PipelineData::empty());
|
||||
}
|
||||
|
||||
if clear {
|
||||
let _ = std::fs::remove_file(history_path);
|
||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
||||
Ok(PipelineData::empty())
|
||||
} else {
|
||||
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
SqliteBackedHistory::with_file(history_path.clone().into(), None, None)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
HistoryFileFormat::Plaintext => FileBackedHistory::with_file(
|
||||
history.max_size as usize,
|
||||
history_path.clone().into(),
|
||||
)
|
||||
let long = call.has_flag(engine_state, stack, "long")?;
|
||||
let signals = engine_state.signals().clone();
|
||||
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
})
|
||||
.ok(),
|
||||
};
|
||||
|
||||
match history.file_format {
|
||||
HistoryFileFormat::Plaintext => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||
Value::record(
|
||||
record! {
|
||||
"command" => Value::string(entry.command_line, head),
|
||||
"index" => Value::int(idx as i64, head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
})
|
||||
})
|
||||
.ok_or(ShellError::FileNotFound {
|
||||
file: history_path.display().to_string(),
|
||||
span: head,
|
||||
})?
|
||||
.into_pipeline_data(head, signals)),
|
||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||
create_history_record(idx, entry, long, head)
|
||||
})
|
||||
})
|
||||
.ok_or(ShellError::FileNotFound {
|
||||
file: history_path.display().to_string(),
|
||||
span: head,
|
||||
})?
|
||||
.into_pipeline_data(head, signals)),
|
||||
}
|
||||
.ok()
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::ConfigDirNotFound { span: Some(head) })
|
||||
HistoryFileFormat::Plaintext => {
|
||||
FileBackedHistory::with_file(history.max_size as usize, history_path.clone())
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
};
|
||||
match history.file_format {
|
||||
HistoryFileFormat::Plaintext => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||
Value::record(
|
||||
record! {
|
||||
"command" => Value::string(entry.command_line, head),
|
||||
"index" => Value::int(idx as i64, head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
})
|
||||
})
|
||||
.ok_or(ShellError::FileNotFound {
|
||||
file: history_path.display().to_string(),
|
||||
span: head,
|
||||
})?
|
||||
.into_pipeline_data(head, signals)),
|
||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
entries
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
||||
})
|
||||
.ok_or(ShellError::FileNotFound {
|
||||
file: history_path.display().to_string(),
|
||||
span: head,
|
||||
})?
|
||||
.into_pipeline_data(head, signals)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ pub trait Completer {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
|
@ -158,7 +158,7 @@ impl Completer for CommandCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
@ -195,7 +195,7 @@ impl Completer for CommandCompletion {
|
||||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), subcommands, options);
|
||||
return sort_suggestions(&String::from_utf8_lossy(prefix), subcommands, options);
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
@ -220,7 +220,7 @@ impl Completer for CommandCompletion {
|
||||
vec![]
|
||||
};
|
||||
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), commands, options)
|
||||
sort_suggestions(&String::from_utf8_lossy(prefix), commands, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
|
||||
};
|
||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||
use nu_engine::eval_block;
|
||||
@ -25,7 +25,7 @@ impl NuCompleter {
|
||||
pub fn new(engine_state: Arc<EngineState>, stack: Arc<Stack>) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack: Stack::with_parent(stack).reset_out_dest().capture(),
|
||||
stack: Stack::with_parent(stack).reset_out_dest().collect_value(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ impl NuCompleter {
|
||||
&self,
|
||||
completer: &mut T,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
new_span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
@ -55,7 +55,7 @@ impl NuCompleter {
|
||||
completer.fetch(
|
||||
working_set,
|
||||
&self.stack,
|
||||
prefix.clone(),
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
@ -170,23 +170,38 @@ impl NuCompleter {
|
||||
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.
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
let mut prefix = working_set.get_span_contents(flat.0);
|
||||
let index = pos - flat.0.start;
|
||||
prefix.drain(index..);
|
||||
prefix = &prefix[..index];
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
let mut completer =
|
||||
let mut variable_names_completer =
|
||||
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
let mut variable_completions = self.process_completion(
|
||||
&mut variable_names_completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
fake_offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
let mut variable_operations_completer =
|
||||
OperatorCompletion::new(pipeline_element.expr.clone());
|
||||
|
||||
let mut variable_operations_completions = self.process_completion(
|
||||
&mut variable_operations_completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
fake_offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
variable_completions.append(&mut variable_operations_completions);
|
||||
return variable_completions;
|
||||
}
|
||||
|
||||
// Flags completion
|
||||
@ -196,7 +211,7 @@ impl NuCompleter {
|
||||
let result = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
prefix,
|
||||
new_span,
|
||||
fake_offset,
|
||||
pos,
|
||||
@ -262,6 +277,26 @@ impl NuCompleter {
|
||||
} else if prev_expr_str == b"ls" {
|
||||
let mut completer = FileCompletion::new();
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
fake_offset,
|
||||
pos,
|
||||
);
|
||||
} else if matches!(
|
||||
previous_expr.1,
|
||||
FlatShape::Float
|
||||
| FlatShape::Int
|
||||
| FlatShape::String
|
||||
| FlatShape::List
|
||||
| FlatShape::Bool
|
||||
| FlatShape::Variable(_)
|
||||
) {
|
||||
let mut completer =
|
||||
OperatorCompletion::new(pipeline_element.expr.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
@ -327,7 +362,7 @@ impl NuCompleter {
|
||||
let mut out: Vec<_> = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
prefix,
|
||||
new_span,
|
||||
fake_offset,
|
||||
pos,
|
||||
@ -533,6 +568,11 @@ mod completer_tests {
|
||||
|
||||
let mut completer = NuCompleter::new(engine_state.into(), Arc::new(Stack::new()));
|
||||
let dataset = [
|
||||
("1 bit-sh", true, "b", vec!["bit-shl", "bit-shr"]),
|
||||
("1.0 bit-sh", false, "b", vec![]),
|
||||
("1 m", true, "m", vec!["mod"]),
|
||||
("1.0 m", true, "m", vec!["mod"]),
|
||||
("\"a\" s", true, "s", vec!["starts-with"]),
|
||||
("sudo", false, "", Vec::new()),
|
||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||
(" sudo", false, "", Vec::new()),
|
||||
|
@ -180,7 +180,7 @@ pub fn complete_item(
|
||||
&& engine_state.config.use_ansi_coloring)
|
||||
.then(|| {
|
||||
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(),
|
||||
None => None,
|
||||
};
|
||||
get_ls_colors(ls_colors_env_str)
|
||||
@ -267,8 +267,10 @@ pub fn escape_path(path: String, dir: bool) -> String {
|
||||
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
||||
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
||||
let maybe_flag = path.starts_with('-');
|
||||
let maybe_variable = path.starts_with('$');
|
||||
let maybe_number = path.parse::<f64>().is_ok();
|
||||
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_number {
|
||||
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_variable || maybe_number
|
||||
{
|
||||
format!("`{path}`")
|
||||
} else {
|
||||
path
|
||||
@ -333,7 +335,7 @@ pub fn sort_completions<T>(
|
||||
} else {
|
||||
matcher = matcher.ignore_case();
|
||||
};
|
||||
items.sort_by(|a, b| {
|
||||
items.sort_unstable_by(|a, b| {
|
||||
let a_str = get_value(a);
|
||||
let b_str = get_value(b);
|
||||
let a_score = matcher.fuzzy_match(a_str, prefix).unwrap_or_default();
|
||||
@ -341,7 +343,7 @@ pub fn sort_completions<T>(
|
||||
b_score.cmp(&a_score).then(a_str.cmp(b_str))
|
||||
});
|
||||
} else {
|
||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
items.sort_unstable_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
}
|
||||
|
||||
items
|
||||
|
@ -7,7 +7,7 @@ use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, Expression},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Stack, StateWorkingSet},
|
||||
CompletionSort, PipelineData, Span, Type, Value,
|
||||
CompletionSort, DeclId, PipelineData, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use std::collections::HashMap;
|
||||
@ -16,12 +16,12 @@ use super::completion_common::sort_suggestions;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
stack: Stack,
|
||||
decl_id: usize,
|
||||
decl_id: DeclId,
|
||||
line: String,
|
||||
}
|
||||
|
||||
impl CustomCompletion {
|
||||
pub fn new(stack: Stack, decl_id: usize, line: String) -> Self {
|
||||
pub fn new(stack: Stack, decl_id: DeclId, line: String) -> Self {
|
||||
Self {
|
||||
stack,
|
||||
decl_id,
|
||||
@ -35,7 +35,7 @@ impl Completer for CustomCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
@ -126,8 +126,8 @@ impl Completer for CustomCompletion {
|
||||
let options = custom_completion_options
|
||||
.as_ref()
|
||||
.unwrap_or(completion_options);
|
||||
let suggestions = filter(&prefix, suggestions, options);
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, options)
|
||||
let suggestions = filter(prefix, suggestions, options);
|
||||
sort_suggestions(&String::from_utf8_lossy(prefix), suggestions, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,13 +26,13 @@ impl Completer for DirectoryCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
|
||||
let AdjustView { prefix, span, .. } = adjust_if_intermediate(prefix, working_set, span);
|
||||
|
||||
// Filter only the folders
|
||||
#[allow(deprecated)]
|
||||
|
@ -22,13 +22,13 @@ impl Completer for DotNuCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
||||
let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
|
||||
let mut search_dirs: Vec<String> = vec![];
|
||||
|
||||
// If prefix_str is only a word we want to search in the current dir
|
||||
|
@ -27,7 +27,7 @@ impl Completer for FileCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
@ -37,7 +37,7 @@ impl Completer for FileCompletion {
|
||||
prefix,
|
||||
span,
|
||||
readjusted,
|
||||
} = adjust_if_intermediate(&prefix, working_set, span);
|
||||
} = adjust_if_intermediate(prefix, working_set, span);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let items: Vec<_> = complete_item(
|
||||
|
@ -24,7 +24,7 @@ impl Completer for FlagCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
@ -44,7 +44,7 @@ impl Completer for FlagCompletion {
|
||||
short.encode_utf8(&mut named);
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
if options.match_algorithm.matches_u8(&named, prefix) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
@ -70,7 +70,7 @@ impl Completer for FlagCompletion {
|
||||
named.insert(0, b'-');
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
if options.match_algorithm.matches_u8(&named, prefix) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
@ -88,7 +88,7 @@ impl Completer for FlagCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, options);
|
||||
return sort_suggestions(&String::from_utf8_lossy(prefix), output, options);
|
||||
}
|
||||
|
||||
vec![]
|
||||
|
@ -8,6 +8,7 @@ mod directory_completions;
|
||||
mod dotnu_completions;
|
||||
mod file_completions;
|
||||
mod flag_completions;
|
||||
mod operator_completions;
|
||||
mod variable_completions;
|
||||
|
||||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||
@ -19,4 +20,5 @@ pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
pub use file_completions::{file_path_completion, matches, FileCompletion};
|
||||
pub use flag_completions::FlagCompletion;
|
||||
pub use operator_completions::OperatorCompletion;
|
||||
pub use variable_completions::VariableCompletion;
|
||||
|
180
crates/nu-cli/src/completions/operator_completions.rs
Normal file
180
crates/nu-cli/src/completions/operator_completions.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use crate::completions::{
|
||||
Completer, CompletionOptions, MatchAlgorithm, SemanticSuggestion, SuggestionKind,
|
||||
};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::{Stack, StateWorkingSet},
|
||||
Span, Type,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OperatorCompletion {
|
||||
previous_expr: Expression,
|
||||
}
|
||||
|
||||
impl OperatorCompletion {
|
||||
pub fn new(previous_expr: Expression) -> Self {
|
||||
OperatorCompletion { previous_expr }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for OperatorCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_stack: &Stack,
|
||||
_prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
_options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
//Check if int, float, or string
|
||||
let partial = std::str::from_utf8(working_set.get_span_contents(span)).unwrap_or("");
|
||||
let op = match &self.previous_expr.expr {
|
||||
Expr::BinaryOp(x, _, _) => &x.expr,
|
||||
_ => {
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let possible_operations = match op {
|
||||
Expr::Int(_) => vec![
|
||||
("+", "Add (Plus)"),
|
||||
("-", "Subtract (Minus)"),
|
||||
("*", "Multiply"),
|
||||
("/", "Divide"),
|
||||
("==", "Equal to"),
|
||||
("!=", "Not equal to"),
|
||||
("//", "Floor division"),
|
||||
("<", "Less than"),
|
||||
(">", "Greater than"),
|
||||
("<=", "Less than or equal to"),
|
||||
(">=", "Greater than or equal to"),
|
||||
("mod", "Floor division remainder (Modulo)"),
|
||||
("**", "Power of"),
|
||||
("bit-or", "Bitwise OR"),
|
||||
("bit-xor", "Bitwise exclusive OR"),
|
||||
("bit-and", "Bitwise AND"),
|
||||
("bit-shl", "Bitwise shift left"),
|
||||
("bit-shr", "Bitwise shift right"),
|
||||
("in", "Is a member of (doesn't use regex)"),
|
||||
("not-in", "Is not a member of (doesn't use regex)"),
|
||||
(
|
||||
"++",
|
||||
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||
),
|
||||
],
|
||||
Expr::String(_) => vec![
|
||||
("=~", "Contains regex match"),
|
||||
("!~", "Does not contain regex match"),
|
||||
(
|
||||
"++",
|
||||
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||
),
|
||||
("in", "Is a member of (doesn't use regex)"),
|
||||
("not-in", "Is not a member of (doesn't use regex)"),
|
||||
("starts-with", "Starts with"),
|
||||
("ends-with", "Ends with"),
|
||||
],
|
||||
Expr::Float(_) => vec![
|
||||
("+", "Add (Plus)"),
|
||||
("-", "Subtract (Minus)"),
|
||||
("*", "Multiply"),
|
||||
("/", "Divide"),
|
||||
("==", "Equal to"),
|
||||
("!=", "Not equal to"),
|
||||
("//", "Floor division"),
|
||||
("<", "Less than"),
|
||||
(">", "Greater than"),
|
||||
("<=", "Less than or equal to"),
|
||||
(">=", "Greater than or equal to"),
|
||||
("mod", "Floor division remainder (Modulo)"),
|
||||
("**", "Power of"),
|
||||
("in", "Is a member of (doesn't use regex)"),
|
||||
("not-in", "Is not a member of (doesn't use regex)"),
|
||||
(
|
||||
"++",
|
||||
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||
),
|
||||
],
|
||||
Expr::Bool(_) => vec![
|
||||
(
|
||||
"and",
|
||||
"Both values are true (short-circuits when first value is false)",
|
||||
),
|
||||
(
|
||||
"or",
|
||||
"Either value is true (short-circuits when first value is true)",
|
||||
),
|
||||
("xor", "One value is true and the other is false"),
|
||||
("not", "Negates a value or expression"),
|
||||
("in", "Is a member of (doesn't use regex)"),
|
||||
("not-in", "Is not a member of (doesn't use regex)"),
|
||||
(
|
||||
"++",
|
||||
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||
),
|
||||
],
|
||||
Expr::FullCellPath(path) => match path.head.expr {
|
||||
Expr::List(_) => vec![(
|
||||
"++",
|
||||
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||
)],
|
||||
Expr::Var(id) => get_variable_completions(id, working_set),
|
||||
_ => vec![],
|
||||
},
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let match_algorithm = MatchAlgorithm::Prefix;
|
||||
let input_fuzzy_search =
|
||||
|(operator, _): &(&str, &str)| match_algorithm.matches_str(operator, partial);
|
||||
|
||||
possible_operations
|
||||
.into_iter()
|
||||
.filter(input_fuzzy_search)
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.0.to_string(),
|
||||
description: Some(x.1.to_string()),
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Command(
|
||||
nu_protocol::engine::CommandType::Builtin,
|
||||
)),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_variable_completions<'a>(
|
||||
id: nu_protocol::Id<nu_protocol::marker::Var>,
|
||||
working_set: &StateWorkingSet,
|
||||
) -> Vec<(&'a str, &'a str)> {
|
||||
let var = working_set.get_variable(id);
|
||||
if !var.mutable {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
match var.ty {
|
||||
Type::List(_) | Type::String | Type::Binary => vec![
|
||||
(
|
||||
"++=",
|
||||
"Appends a list, a value, a string, or a binary value to a variable.",
|
||||
),
|
||||
("=", "Assigns a value to a variable."),
|
||||
],
|
||||
|
||||
Type::Int | Type::Float => vec![
|
||||
("=", "Assigns a value to a variable."),
|
||||
("+=", "Adds a value to a variable."),
|
||||
("-=", "Subtracts a value from a variable."),
|
||||
("*=", "Multiplies a variable by a value"),
|
||||
("/=", "Divides a variable by a value."),
|
||||
],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ impl Completer for VariableCompletion {
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
stack: &Stack,
|
||||
prefix: Vec<u8>,
|
||||
prefix: &[u8],
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_pos: usize,
|
||||
@ -42,7 +42,7 @@ impl Completer for VariableCompletion {
|
||||
end: span.end - offset,
|
||||
};
|
||||
let sublevels_count = self.var_context.1.len();
|
||||
let prefix_str = String::from_utf8_lossy(&prefix);
|
||||
let prefix_str = String::from_utf8_lossy(prefix);
|
||||
|
||||
// Completions for the given variable
|
||||
if !var_str.is_empty() {
|
||||
@ -66,7 +66,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
@ -80,7 +80,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
env_var.0.as_bytes(),
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
@ -111,7 +111,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
@ -133,7 +133,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
@ -149,7 +149,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
builtin.as_bytes(),
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
@ -173,7 +173,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
@ -201,7 +201,7 @@ impl Completer for VariableCompletion {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
prefix,
|
||||
) {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
|
@ -5,7 +5,7 @@ use nu_path::canonicalize_with;
|
||||
use nu_protocol::{engine::StateWorkingSet, ParseError, PluginRegistryFile, Spanned};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
report_shell_error, HistoryFileFormat, PipelineData,
|
||||
report_shell_error, PipelineData,
|
||||
};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_utils::perf;
|
||||
@ -16,15 +16,8 @@ const PLUGIN_FILE: &str = "plugin.msgpackz";
|
||||
#[cfg(feature = "plugin")]
|
||||
const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
||||
|
||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||
use nu_protocol::ShellError;
|
||||
use std::path::Path;
|
||||
|
||||
@ -52,7 +45,7 @@ pub fn read_plugin_file(
|
||||
let mut start_time = std::time::Instant::now();
|
||||
// Reading signatures from plugin registry file
|
||||
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
||||
add_plugin_file(engine_state, plugin_file.clone());
|
||||
perf!(
|
||||
"add plugin file to engine_state",
|
||||
start_time,
|
||||
@ -70,8 +63,7 @@ pub fn read_plugin_file(
|
||||
log::warn!("Plugin file not found: {}", plugin_path.display());
|
||||
|
||||
// Try migration of an old plugin file if this wasn't a custom plugin file
|
||||
if plugin_file.is_none() && migrate_old_plugin_file(engine_state, storage_path)
|
||||
{
|
||||
if plugin_file.is_none() && migrate_old_plugin_file(engine_state) {
|
||||
let Ok(file) = std::fs::File::open(&plugin_path) else {
|
||||
log::warn!("Failed to load newly migrated plugin file");
|
||||
return;
|
||||
@ -159,11 +151,7 @@ pub fn read_plugin_file(
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
pub fn add_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||
use std::path::Path;
|
||||
|
||||
use nu_protocol::report_parse_error;
|
||||
@ -189,9 +177,8 @@ pub fn add_plugin_file(
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
} else if let Some(plugin_path) = nu_path::nu_config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
let mut plugin_path =
|
||||
canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path.into());
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
@ -228,33 +215,15 @@ pub fn eval_config_contents(
|
||||
engine_state.file = prev_file;
|
||||
|
||||
// Merge the environment in case env vars changed in the config
|
||||
match engine_state.cwd(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
if let Err(e) = engine_state.merge_env(stack) {
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
|
||||
nu_path::config_dir().map(|mut history_path| {
|
||||
history_path.push(storage_path);
|
||||
history_path.push(match mode {
|
||||
HistoryFileFormat::Plaintext => HISTORY_FILE_TXT,
|
||||
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
||||
});
|
||||
history_path.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -> bool {
|
||||
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
||||
use nu_protocol::{
|
||||
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
|
||||
ShellError,
|
||||
@ -267,10 +236,9 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {
|
||||
dir.push(storage_path);
|
||||
nu_path::canonicalize_with(dir, &cwd).ok()
|
||||
}) else {
|
||||
let Some(config_dir) =
|
||||
nu_path::nu_config_dir().and_then(|dir| nu_path::canonicalize_with(dir, &cwd).ok())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,6 @@ pub use config_files::eval_config_contents;
|
||||
pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use menus::NuHelpCompleter;
|
||||
pub use nu_cmd_base::util::get_init_cwd;
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
|
@ -2,7 +2,7 @@ use nu_engine::eval_block;
|
||||
use nu_protocol::{
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack},
|
||||
IntoPipelineData, Span, Value,
|
||||
BlockId, IntoPipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
||||
use std::sync::Arc;
|
||||
@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||
const SELECTION_CHAR: char = '!';
|
||||
|
||||
pub struct NuMenuCompleter {
|
||||
block_id: usize,
|
||||
block_id: BlockId,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -19,7 +19,7 @@ pub struct NuMenuCompleter {
|
||||
|
||||
impl NuMenuCompleter {
|
||||
pub fn new(
|
||||
block_id: usize,
|
||||
block_id: BlockId,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -28,7 +28,7 @@ impl NuMenuCompleter {
|
||||
Self {
|
||||
block_id,
|
||||
span,
|
||||
stack: stack.reset_out_dest().capture(),
|
||||
stack: stack.reset_out_dest().collect_value(),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ByteStreamSource;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Print;
|
||||
@ -50,7 +51,7 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
mut input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
||||
@ -69,6 +70,11 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
}
|
||||
}
|
||||
} else if !input.is_nothing() {
|
||||
if let PipelineData::ByteStream(stream, _) = &mut input {
|
||||
if let ByteStreamSource::Child(child) = stream.source_mut() {
|
||||
child.ignore_error(true);
|
||||
}
|
||||
}
|
||||
if raw {
|
||||
input.print_raw(engine_state, no_newline, to_stderr)?;
|
||||
} else {
|
||||
|
@ -1,10 +1,7 @@
|
||||
use crate::prompt_update::{
|
||||
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Value,
|
||||
};
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::{
|
||||
@ -124,8 +121,11 @@ impl Prompt for NushellPrompt {
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
if self.shell_integration_osc633 {
|
||||
if self.stack.get_env_var(&self.engine_state, "TERM_PROGRAM")
|
||||
== Some(Value::test_string("vscode"))
|
||||
if self
|
||||
.stack
|
||||
.get_env_var(&self.engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
// We're in vscode and we have osc633 enabled
|
||||
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
|
||||
|
@ -68,7 +68,7 @@ fn get_prompt_string(
|
||||
.get_env_var(engine_state, prompt)
|
||||
.and_then(|v| match v {
|
||||
Value::Closure { val, .. } => {
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, *val)
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
|
||||
.run_with_input(PipelineData::Empty);
|
||||
|
||||
trace!(
|
||||
@ -119,7 +119,11 @@ pub(crate) fn update_prompt(
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration.osc633 {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
if stack
|
||||
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
// We're in vscode and we have osc633 enabled
|
||||
Some(format!(
|
||||
"{VSCODE_PRE_PROMPT_MARKER}{configured_left_prompt_string}{VSCODE_POST_PROMPT_MARKER}"
|
||||
|
@ -5,11 +5,10 @@ use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
create_menus,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
||||
ShellError, Span, Value,
|
||||
extract_value, Config, EditBindings, FromValue, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||
Record, ShellError, Span, Type, Value,
|
||||
};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||
@ -36,6 +35,41 @@ const DEFAULT_COMPLETION_MENU: &str = r#"
|
||||
}
|
||||
}"#;
|
||||
|
||||
const DEFAULT_IDE_COMPLETION_MENU: &str = r#"
|
||||
{
|
||||
name: ide_completion_menu
|
||||
only_buffer_difference: false
|
||||
marker: "| "
|
||||
type: {
|
||||
layout: ide
|
||||
min_completion_width: 0,
|
||||
max_completion_width: 50,
|
||||
max_completion_height: 10, # will be limited by the available lines in the terminal
|
||||
padding: 0,
|
||||
border: true,
|
||||
cursor_offset: 0,
|
||||
description_mode: "prefer_right"
|
||||
min_description_width: 0
|
||||
max_description_width: 50
|
||||
max_description_height: 10
|
||||
description_offset: 1
|
||||
# If true, the cursor pos will be corrected, so the suggestions match up with the typed text
|
||||
#
|
||||
# C:\> str
|
||||
# str join
|
||||
# str trim
|
||||
# str split
|
||||
correct_cursor_pos: false
|
||||
}
|
||||
style: {
|
||||
text: green
|
||||
selected_text: { attr: r }
|
||||
description_text: yellow
|
||||
match_text: { attr: u }
|
||||
selected_match_text: { attr: ur }
|
||||
}
|
||||
}"#;
|
||||
|
||||
const DEFAULT_HISTORY_MENU: &str = r#"
|
||||
{
|
||||
name: history_menu
|
||||
@ -95,6 +129,7 @@ pub(crate) fn add_menus(
|
||||
// Checking if the default menus have been added from the config file
|
||||
let default_menus = [
|
||||
("completion_menu", DEFAULT_COMPLETION_MENU),
|
||||
("ide_completion_menu", DEFAULT_IDE_COMPLETION_MENU),
|
||||
("history_menu", DEFAULT_HISTORY_MENU),
|
||||
("help_menu", DEFAULT_HELP_MENU),
|
||||
];
|
||||
@ -122,7 +157,7 @@ pub(crate) fn add_menus(
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let mut temp_stack = Stack::new().capture();
|
||||
let mut temp_stack = Stack::new().collect_value();
|
||||
let input = PipelineData::Empty;
|
||||
menu_eval_results.push(eval_block::<WithoutDebug>(
|
||||
&engine_state,
|
||||
@ -137,15 +172,13 @@ pub(crate) fn add_menus(
|
||||
|
||||
for res in menu_eval_results.into_iter() {
|
||||
if let PipelineData::Value(value, None) = res {
|
||||
for menu in create_menus(&value)? {
|
||||
line_editor = add_menu(
|
||||
line_editor,
|
||||
&menu,
|
||||
new_engine_state_ref.clone(),
|
||||
stack,
|
||||
config.clone(),
|
||||
)?;
|
||||
}
|
||||
line_editor = add_menu(
|
||||
line_editor,
|
||||
&ParsedMenu::from_value(value)?,
|
||||
new_engine_state_ref.clone(),
|
||||
stack,
|
||||
config.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,22 +201,22 @@ fn add_menu(
|
||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "columnar, list, ide or description".to_string(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
str => Err(ShellError::InvalidValue {
|
||||
valid: "'columnar', 'list', 'ide', or 'description'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "only record type".to_string(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::record(),
|
||||
actual: menu.r#type.get_type(),
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
||||
fn get_style(record: &Record, name: &'static str, span: Span) -> Option<Style> {
|
||||
extract_value(name, record, span)
|
||||
.ok()
|
||||
.map(|text| match text {
|
||||
@ -262,30 +295,23 @@ pub(crate) fn add_columnar_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(columnar_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(config),
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
}),
|
||||
}
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(columnar_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::EngineCompleter(Box::new(columnar_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds a search menu to the line editor
|
||||
@ -318,30 +344,23 @@ pub(crate) fn add_list_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(list_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(list_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
span: menu.source.span(),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::HistoryMenu(Box::new(list_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds an IDE menu to the line editor
|
||||
@ -416,9 +435,9 @@ pub(crate) fn add_ide_menu(
|
||||
vertical,
|
||||
)
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "bool or record".to_string(),
|
||||
value: border.to_abbreviated_string(&config),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("bool or record"),
|
||||
actual: border.get_type(),
|
||||
span: border.span(),
|
||||
});
|
||||
}
|
||||
@ -439,10 +458,10 @@ pub(crate) fn add_ide_menu(
|
||||
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
||||
value: description_mode.to_abbreviated_string(&config),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "'left', 'right', or 'prefer_right'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: description_mode.span(),
|
||||
});
|
||||
}
|
||||
@ -499,30 +518,23 @@ pub(crate) fn add_ide_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(ide_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
}),
|
||||
}
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(ide_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::EngineCompleter(Box::new(ide_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds a description menu to the line editor
|
||||
@ -587,34 +599,27 @@ pub(crate) fn add_description_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
let completer = Box::new(NuHelpCompleter::new(engine_state, config));
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer,
|
||||
}))
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
} else {
|
||||
let menu_completer = NuHelpCompleter::new(engine_state, config);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "closure or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
span: menu.source.span(),
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
@ -629,6 +634,16 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
]),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char(' '),
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::Menu("ide_completion_menu".to_string()),
|
||||
ReedlineEvent::MenuNext,
|
||||
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||
]),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::SHIFT,
|
||||
KeyCode::BackTab,
|
||||
@ -728,9 +743,9 @@ fn add_keybinding(
|
||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
||||
m => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "emacs, vi_insert or vi_normal".to_string(),
|
||||
value: m.to_string(),
|
||||
str => Err(ShellError::InvalidValue {
|
||||
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
@ -748,9 +763,9 @@ fn add_keybinding(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "string or list of strings".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string or list<string>"),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
}
|
||||
@ -761,91 +776,107 @@ fn add_parsed_keybinding(
|
||||
keybinding: &ParsedKeybinding,
|
||||
config: &Config,
|
||||
) -> Result<(), ShellError> {
|
||||
let modifier = match keybinding
|
||||
.modifier
|
||||
.to_expanded_string("", config)
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"control" => KeyModifiers::CONTROL,
|
||||
"shift" => KeyModifiers::SHIFT,
|
||||
"alt" => KeyModifiers::ALT,
|
||||
"none" => KeyModifiers::NONE,
|
||||
"shift_alt" | "alt_shift" => KeyModifiers::SHIFT | KeyModifiers::ALT,
|
||||
"control_shift" | "shift_control" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
||||
"control_alt" | "alt_control" => KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
"control_alt_shift" | "control_shift_alt" => {
|
||||
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||
value: keybinding.modifier.to_abbreviated_string(config),
|
||||
span: keybinding.modifier.span(),
|
||||
})
|
||||
}
|
||||
let Ok(modifier_str) = keybinding.modifier.as_str() else {
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: keybinding.modifier.get_type(),
|
||||
span: keybinding.modifier.span(),
|
||||
});
|
||||
};
|
||||
|
||||
let keycode = match keybinding
|
||||
.keycode
|
||||
.to_expanded_string("", config)
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"enter" => KeyCode::Enter,
|
||||
c if c.starts_with("char_") => {
|
||||
let mut char_iter = c.chars().skip(5);
|
||||
let pos1 = char_iter.next();
|
||||
let pos2 = char_iter.next();
|
||||
|
||||
let char = if let (Some(char), None) = (pos1, pos2) {
|
||||
char
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "char_<CHAR: unicode codepoint>".to_string(),
|
||||
value: c.to_string(),
|
||||
span: keybinding.keycode.span(),
|
||||
});
|
||||
};
|
||||
|
||||
KeyCode::Char(char)
|
||||
let mut modifier = KeyModifiers::NONE;
|
||||
if !str::eq_ignore_ascii_case(modifier_str, "none") {
|
||||
for part in modifier_str.split('_') {
|
||||
match part.to_ascii_lowercase().as_str() {
|
||||
"control" => modifier |= KeyModifiers::CONTROL,
|
||||
"shift" => modifier |= KeyModifiers::SHIFT,
|
||||
"alt" => modifier |= KeyModifiers::ALT,
|
||||
"super" => modifier |= KeyModifiers::SUPER,
|
||||
"hyper" => modifier |= KeyModifiers::HYPER,
|
||||
"meta" => modifier |= KeyModifiers::META,
|
||||
_ => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "'control', 'shift', 'alt', 'super', 'hyper', 'meta', or 'none'"
|
||||
.into(),
|
||||
actual: format!("'{part}'"),
|
||||
span: keybinding.modifier.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
"right" => KeyCode::Right,
|
||||
"home" => KeyCode::Home,
|
||||
"end" => KeyCode::End,
|
||||
"pageup" => KeyCode::PageUp,
|
||||
"pagedown" => KeyCode::PageDown,
|
||||
"tab" => KeyCode::Tab,
|
||||
"backtab" => KeyCode::BackTab,
|
||||
"delete" => KeyCode::Delete,
|
||||
"insert" => KeyCode::Insert,
|
||||
c if c.starts_with('f') => {
|
||||
let fn_num: u8 = c[1..]
|
||||
}
|
||||
|
||||
let Ok(keycode) = keybinding.keycode.as_str() else {
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: keybinding.keycode.get_type(),
|
||||
span: keybinding.keycode.span(),
|
||||
});
|
||||
};
|
||||
|
||||
let keycode_lower = keycode.to_ascii_lowercase();
|
||||
|
||||
let keycode = if let Some(rest) = keycode_lower.strip_prefix("char_") {
|
||||
let error = |valid: &str, actual: &str| ShellError::InvalidValue {
|
||||
valid: valid.into(),
|
||||
actual: actual.into(),
|
||||
span: keybinding.keycode.span(),
|
||||
};
|
||||
|
||||
let mut char_iter = rest.chars();
|
||||
let char = match (char_iter.next(), char_iter.next()) {
|
||||
(Some(char), None) => char,
|
||||
(Some('u'), Some(_)) => {
|
||||
// This will never panic as we know there are at least two symbols
|
||||
let Ok(code_point) = u32::from_str_radix(&rest[1..], 16) else {
|
||||
return Err(error("a valid hex code", keycode));
|
||||
};
|
||||
|
||||
char::from_u32(code_point).ok_or(error("a valid Unicode code point", keycode))?
|
||||
}
|
||||
_ => return Err(error("'char_<char>' or 'char_u<hex code>'", keycode)),
|
||||
};
|
||||
|
||||
KeyCode::Char(char)
|
||||
} else {
|
||||
match keycode_lower.as_str() {
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"enter" => KeyCode::Enter,
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
"right" => KeyCode::Right,
|
||||
"home" => KeyCode::Home,
|
||||
"end" => KeyCode::End,
|
||||
"pageup" => KeyCode::PageUp,
|
||||
"pagedown" => KeyCode::PageDown,
|
||||
"tab" => KeyCode::Tab,
|
||||
"backtab" => KeyCode::BackTab,
|
||||
"delete" => KeyCode::Delete,
|
||||
"insert" => KeyCode::Insert,
|
||||
c if c.starts_with('f') => c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| matches!(num, 1..=20))
|
||||
.ok_or(ShellError::UnsupportedConfigValue {
|
||||
expected: "(f1|f2|...|f20)".to_string(),
|
||||
value: format!("unknown function key: {c}"),
|
||||
.filter(|num| (1..=20).contains(num))
|
||||
.map(KeyCode::F)
|
||||
.ok_or(ShellError::InvalidValue {
|
||||
valid: "'f1', 'f2', ..., or 'f20'".into(),
|
||||
actual: format!("'{keycode}'"),
|
||||
span: keybinding.keycode.span(),
|
||||
})?;
|
||||
KeyCode::F(fn_num)
|
||||
}
|
||||
"null" => KeyCode::Null,
|
||||
"esc" | "escape" => KeyCode::Esc,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "crossterm KeyCode".to_string(),
|
||||
value: keybinding.keycode.to_abbreviated_string(config),
|
||||
span: keybinding.keycode.span(),
|
||||
})
|
||||
})?,
|
||||
"null" => KeyCode::Null,
|
||||
"esc" | "escape" => KeyCode::Esc,
|
||||
_ => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a crossterm KeyCode".into(),
|
||||
actual: format!("'{keycode}'"),
|
||||
span: keybinding.keycode.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(event) = parse_event(&keybinding.event, config)? {
|
||||
keybindings.add_binding(modifier, keycode, event);
|
||||
} else {
|
||||
@ -867,8 +898,8 @@ impl<'config> EventType<'config> {
|
||||
.map(Self::Send)
|
||||
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
||||
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
||||
.map_err(|_| ShellError::MissingConfigValue {
|
||||
missing_value: "send, edit or until".to_string(),
|
||||
.map_err(|_| ShellError::MissingRequiredColumn {
|
||||
column: "'send', 'edit', or 'until'",
|
||||
span,
|
||||
})
|
||||
}
|
||||
@ -906,9 +937,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
.iter()
|
||||
.map(|value| match parse_event(value, config) {
|
||||
Ok(inner) => match inner {
|
||||
None => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "List containing valid events".to_string(),
|
||||
value: "Nothing value (null)".to_string(),
|
||||
None => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record or table"),
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
}),
|
||||
Some(event) => Ok(event),
|
||||
@ -919,9 +950,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
|
||||
Ok(Some(ReedlineEvent::UntilFound(events)))
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "list of events".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::list(Type::Any),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
},
|
||||
@ -931,9 +962,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
.iter()
|
||||
.map(|value| match parse_event(value, config) {
|
||||
Ok(inner) => match inner {
|
||||
None => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "List containing valid events".to_string(),
|
||||
value: "Nothing value (null)".to_string(),
|
||||
None => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record or table"),
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
}),
|
||||
Some(event) => Ok(event),
|
||||
@ -945,9 +976,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
Ok(Some(ReedlineEvent::Multiple(events)))
|
||||
}
|
||||
Value::Nothing { .. } => Ok(None),
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "record or list of records, null to unbind key".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record, table, or nothing"),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
}
|
||||
@ -996,12 +1027,12 @@ fn event_from_record(
|
||||
let cmd = extract_value("cmd", record, span)?;
|
||||
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||
}
|
||||
v => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "Reedline event".to_string(),
|
||||
value: v.to_string(),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a reedline event".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -1094,7 +1125,7 @@ fn edit_from_record(
|
||||
}
|
||||
"insertchar" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::InsertChar(char)
|
||||
}
|
||||
"insertstring" => {
|
||||
@ -1131,17 +1162,17 @@ fn edit_from_record(
|
||||
"redo" => EditCommand::Redo,
|
||||
"cutrightuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutRightUntil(char)
|
||||
}
|
||||
"cutrightbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutRightBefore(char)
|
||||
}
|
||||
"moverightuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1149,7 +1180,7 @@ fn edit_from_record(
|
||||
}
|
||||
"moverightbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1157,17 +1188,17 @@ fn edit_from_record(
|
||||
}
|
||||
"cutleftuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutLeftUntil(char)
|
||||
}
|
||||
"cutleftbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutLeftBefore(char)
|
||||
}
|
||||
"moveleftuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1175,7 +1206,7 @@ fn edit_from_record(
|
||||
}
|
||||
"moveleftbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1192,28 +1223,36 @@ fn edit_from_record(
|
||||
#[cfg(feature = "system-clipboard")]
|
||||
"pastesystem" => EditCommand::PasteSystem,
|
||||
"selectall" => EditCommand::SelectAll,
|
||||
e => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "reedline EditCommand".to_string(),
|
||||
value: e.to_string(),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a reedline EditCommand".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(edit)
|
||||
}
|
||||
|
||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||
let span = value.span();
|
||||
value
|
||||
.to_expanded_string("", config)
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||
missing_value: "char to insert".to_string(),
|
||||
span,
|
||||
fn extract_char(value: &Value) -> Result<char, ShellError> {
|
||||
if let Ok(str) = value.as_str() {
|
||||
let mut chars = str.chars();
|
||||
match (chars.next(), chars.next()) {
|
||||
(Some(c), None) => Ok(c),
|
||||
_ => Err(ShellError::InvalidValue {
|
||||
valid: "a single character".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: value.span(),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1342,7 +1381,7 @@ mod test {
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_record(&event, span);
|
||||
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
||||
assert!(matches!(b, Err(ShellError::MissingRequiredColumn { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -16,10 +16,7 @@ use crate::{
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use log::{error, trace, warn};
|
||||
use miette::{ErrReport, IntoDiagnostic, Result};
|
||||
use nu_cmd_base::{
|
||||
hook::eval_hook,
|
||||
util::{get_editor, get_guaranteed_cwd},
|
||||
};
|
||||
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
||||
use nu_color_config::StyleComputer;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{convert_env_values, current_dir_str, env_to_strings};
|
||||
@ -53,7 +50,6 @@ use sysinfo::System;
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: Stack,
|
||||
nushell_path: &str,
|
||||
prerun_command: Option<Spanned<String>>,
|
||||
load_std_lib: Option<Spanned<String>>,
|
||||
entire_start_time: Instant,
|
||||
@ -100,7 +96,7 @@ pub fn evaluate_repl(
|
||||
|
||||
unique_stack.set_last_exit_code(0, Span::unknown());
|
||||
|
||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||
let mut line_editor = get_line_editor(engine_state, use_color)?;
|
||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||
|
||||
if let Some(s) = prerun_command {
|
||||
@ -112,8 +108,7 @@ pub fn evaluate_repl(
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
let cwd = get_guaranteed_cwd(engine_state, &unique_stack);
|
||||
engine_state.merge_env(&mut unique_stack, cwd)?;
|
||||
engine_state.merge_env(&mut unique_stack)?;
|
||||
}
|
||||
|
||||
let hostname = System::host_name();
|
||||
@ -135,16 +130,13 @@ pub fn evaluate_repl(
|
||||
// escape a few things because this says so
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
let cmd_text = line_editor.current_buffer_contents().to_string();
|
||||
let len = cmd_text.len();
|
||||
let mut cmd_text_chars = cmd_text[0..len].chars();
|
||||
let mut replaced_cmd_text = String::with_capacity(len);
|
||||
|
||||
let replaced_cmd_text = cmd_text
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\n' => '\x0a',
|
||||
'\r' => '\x0d',
|
||||
'\x1b' => '\x1b',
|
||||
_ => c,
|
||||
})
|
||||
.collect();
|
||||
while let Some(c) = unescape_for_vscode(&mut cmd_text_chars) {
|
||||
replaced_cmd_text.push(c);
|
||||
}
|
||||
|
||||
run_shell_integration_osc633(
|
||||
engine_state,
|
||||
@ -163,7 +155,7 @@ pub fn evaluate_repl(
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
r#"use std banner; banner"#.as_bytes(),
|
||||
r#"banner"#.as_bytes(),
|
||||
"show_banner",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
@ -220,7 +212,7 @@ pub fn evaluate_repl(
|
||||
}
|
||||
Err(_) => {
|
||||
// line_editor is lost in the error case so reconstruct a new one
|
||||
line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||
line_editor = get_line_editor(engine_state, use_color)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,11 +220,29 @@ pub fn evaluate_repl(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_line_editor(
|
||||
engine_state: &mut EngineState,
|
||||
nushell_path: &str,
|
||||
use_color: bool,
|
||||
) -> Result<Reedline> {
|
||||
fn unescape_for_vscode(text: &mut std::str::Chars) -> Option<char> {
|
||||
match text.next() {
|
||||
Some('\\') => match text.next() {
|
||||
Some('0') => Some('\x00'), // NUL '\0' (null character)
|
||||
Some('a') => Some('\x07'), // BEL '\a' (bell)
|
||||
Some('b') => Some('\x08'), // BS '\b' (backspace)
|
||||
Some('t') => Some('\x09'), // HT '\t' (horizontal tab)
|
||||
Some('n') => Some('\x0a'), // LF '\n' (new line)
|
||||
Some('v') => Some('\x0b'), // VT '\v' (vertical tab)
|
||||
Some('f') => Some('\x0c'), // FF '\f' (form feed)
|
||||
Some('r') => Some('\x0d'), // CR '\r' (carriage ret)
|
||||
Some(';') => Some('\x3b'), // semi-colon
|
||||
Some('\\') => Some('\x5c'), // backslash
|
||||
Some('e') => Some('\x1b'), // escape
|
||||
Some(c) => Some(c),
|
||||
None => None,
|
||||
},
|
||||
Some(c) => Some(c),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_line_editor(engine_state: &mut EngineState, use_color: bool) -> Result<Reedline> {
|
||||
let mut start_time = std::time::Instant::now();
|
||||
let mut line_editor = Reedline::create();
|
||||
|
||||
@ -243,7 +253,7 @@ fn get_line_editor(
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||
line_editor = setup_history(engine_state, line_editor, history)?;
|
||||
|
||||
perf!("setup history", start_time, use_color);
|
||||
}
|
||||
@ -280,12 +290,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
hostname,
|
||||
} = ctx;
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, &stack);
|
||||
|
||||
let mut start_time = std::time::Instant::now();
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||
if let Err(err) = engine_state.merge_env(&mut stack) {
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
// Check whether $env.NU_DISABLE_IR is set, so that the user can change it in the REPL
|
||||
@ -363,7 +371,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
))
|
||||
.with_cursor_config(cursor_config);
|
||||
.with_cursor_config(cursor_config)
|
||||
.with_visual_selection_style(nu_ansi_term::Style {
|
||||
is_reverse: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
perf!("reedline builder", start_time, use_color);
|
||||
|
||||
@ -518,8 +530,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
drop(repl);
|
||||
|
||||
if shell_integration_osc633 {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
== Some(Value::test_string("vscode"))
|
||||
if stack
|
||||
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
start_time = Instant::now();
|
||||
|
||||
@ -841,7 +855,7 @@ fn do_auto_cd(
|
||||
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let mut shells = if let Some(v) = shells {
|
||||
v.into_list().unwrap_or_else(|_| vec![cwd])
|
||||
v.clone().into_list().unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
@ -1033,7 +1047,11 @@ fn run_shell_integration_osc633(
|
||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
if stack
|
||||
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
let start_time = Instant::now();
|
||||
|
||||
// If we're in vscode, run their specific ansi escape sequence.
|
||||
@ -1098,7 +1116,6 @@ fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &
|
||||
/// Setup history management for Reedline
|
||||
///
|
||||
fn setup_history(
|
||||
nushell_path: &str,
|
||||
engine_state: &mut EngineState,
|
||||
line_editor: Reedline,
|
||||
history: HistoryConfig,
|
||||
@ -1110,7 +1127,7 @@ fn setup_history(
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) {
|
||||
if let Some(path) = history.file_path() {
|
||||
return update_line_editor_history(
|
||||
engine_state,
|
||||
path,
|
||||
@ -1231,7 +1248,11 @@ fn get_command_finished_marker(
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
|
||||
if shell_integration_osc633 {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
if stack
|
||||
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
// We're in vscode and we have osc633 enabled
|
||||
format!(
|
||||
"{}{}{}",
|
||||
@ -1280,7 +1301,11 @@ fn run_finaliziation_ansi_sequence(
|
||||
) {
|
||||
if shell_integration_osc633 {
|
||||
// Only run osc633 if we are in vscode
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
if stack
|
||||
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
.and_then(|v| v.as_str().ok())
|
||||
== Some("vscode")
|
||||
{
|
||||
let start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(
|
||||
@ -1378,8 +1403,7 @@ fn trailing_slash_looks_like_path() {
|
||||
fn are_session_ids_in_sync() {
|
||||
let engine_state = &mut EngineState::new();
|
||||
let history = engine_state.history_config().unwrap();
|
||||
let history_path =
|
||||
crate::config_files::get_history_path("nushell", history.file_format).unwrap();
|
||||
let history_path = history.file_path().unwrap();
|
||||
let line_editor = reedline::Reedline::create();
|
||||
let history_session_id = reedline::Reedline::create_history_session_id();
|
||||
let line_editor = update_line_editor_history(
|
||||
|
@ -18,11 +18,11 @@ use support::{
|
||||
#[fixture]
|
||||
fn completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "def tst [--mod -s] {}";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -31,11 +31,12 @@ fn completer() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn completer_strings() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -44,7 +45,7 @@ fn completer_strings() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn extern_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
@ -55,7 +56,7 @@ fn extern_completer() -> NuCompleter {
|
||||
-b: string@animals
|
||||
]
|
||||
"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -64,7 +65,7 @@ fn extern_completer() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn completer_strings_with_options() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
# To test that the config setting has no effect on the custom completions
|
||||
@ -81,7 +82,7 @@ fn completer_strings_with_options() -> NuCompleter {
|
||||
}
|
||||
}
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -90,7 +91,7 @@ fn completer_strings_with_options() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn custom_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
@ -104,7 +105,7 @@ fn custom_completer() -> NuCompleter {
|
||||
completer: $external_completer
|
||||
}
|
||||
"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -113,7 +114,7 @@ fn custom_completer() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn subcommand_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
let commands = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
@ -123,7 +124,7 @@ fn subcommand_completer() -> NuCompleter {
|
||||
def "foo aabcrr" [] {}
|
||||
def food [] {}
|
||||
"#;
|
||||
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
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))
|
||||
@ -133,13 +134,13 @@ fn subcommand_completer() -> NuCompleter {
|
||||
#[fixture]
|
||||
fn fuzzy_alpha_sort_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
let config = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
$env.config.completions.sort = "alphabetical"
|
||||
"#;
|
||||
assert!(support::merge_input(config.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(config.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
@ -942,7 +943,7 @@ fn flag_completions() {
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(16, suggestions.len());
|
||||
assert_eq!(18, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
@ -953,6 +954,7 @@ fn flag_completions() {
|
||||
"--long".into(),
|
||||
"--mime-type".into(),
|
||||
"--short-names".into(),
|
||||
"--threads".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
@ -961,6 +963,7 @@ fn flag_completions() {
|
||||
"-l".into(),
|
||||
"-m".into(),
|
||||
"-s".into(),
|
||||
"-t".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
@ -1194,11 +1197,11 @@ fn folder_with_directorycompletions_do_not_collapse_dots() {
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
@ -1309,11 +1312,11 @@ fn variables_completions() {
|
||||
|
||||
#[test]
|
||||
fn alias_of_command_and_flags() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
@ -1328,11 +1331,11 @@ fn alias_of_command_and_flags() {
|
||||
|
||||
#[test]
|
||||
fn alias_of_basic_command() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls "#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
@ -1347,14 +1350,14 @@ fn alias_of_basic_command() {
|
||||
|
||||
#[test]
|
||||
fn alias_of_another_alias() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -la"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
// Create the second alias
|
||||
let alias = r#"alias lf = ll -f"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
@ -1371,7 +1374,7 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
let completer = format!("$env.config.completions.external.completer = {completer}");
|
||||
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||
let (_, _, mut engine_state, mut stack) = new_engine();
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, completer.as_bytes(), false);
|
||||
@ -1387,7 +1390,7 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||
assert!(engine_state.merge_env(&mut stack).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine_state), Arc::new(stack));
|
||||
@ -1576,11 +1579,11 @@ fn sort_fuzzy_completions_in_alphabetical_order(mut fuzzy_alpha_sort_completer:
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7648() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
@ -1595,11 +1598,11 @@ fn alias_offset_bug_7648() {
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7754() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
|
@ -63,7 +63,7 @@ pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
let merge_result = engine_state.merge_env(&mut stack);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -109,7 +109,7 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
let merge_result = engine_state.merge_env(&mut stack);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -144,7 +144,7 @@ pub fn new_quote_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
let merge_result = engine_state.merge_env(&mut stack);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -179,7 +179,7 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
let merge_result = engine_state.merge_env(&mut stack);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -223,7 +223,6 @@ pub fn merge_input(
|
||||
input: &[u8],
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
dir: AbsolutePathBuf,
|
||||
) -> Result<(), ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
@ -246,5 +245,5 @@ pub fn merge_input(
|
||||
.is_ok());
|
||||
|
||||
// Merge environment into the permanent state
|
||||
engine_state.merge_env(stack, &dir)
|
||||
engine_state.merge_env(stack)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.98.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.99.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.99.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::util::get_guaranteed_cwd;
|
||||
use miette::Result;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::parse;
|
||||
@ -19,17 +18,12 @@ pub fn eval_env_change_hook(
|
||||
match hook {
|
||||
Value::Record { val, .. } => {
|
||||
for (env_name, hook_value) in &*val {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let after = stack
|
||||
.get_env_var(engine_state, env_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
let before = engine_state.previous_env_vars.get(env_name);
|
||||
let after = stack.get_env_var(engine_state, env_name);
|
||||
if before != after {
|
||||
let before = before.cloned().unwrap_or_default();
|
||||
let after = after.cloned().unwrap_or_default();
|
||||
|
||||
eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
@ -40,7 +34,7 @@ pub fn eval_env_change_hook(
|
||||
)?;
|
||||
|
||||
Arc::make_mut(&mut engine_state.previous_env_vars)
|
||||
.insert(env_name.to_string(), after);
|
||||
.insert(env_name.clone(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,11 +86,12 @@ pub fn eval_hook(
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
value: "source code with syntax errors".into(),
|
||||
span,
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("Failed to run {hook_name} hook"),
|
||||
msg: "source code has errors".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -167,10 +162,10 @@ pub fn eval_hook(
|
||||
{
|
||||
val
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "boolean output".to_string(),
|
||||
value: "other PipelineData variant".to_string(),
|
||||
span: other_span,
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::Bool,
|
||||
actual: pipeline_data.get_type(),
|
||||
span: pipeline_data.span().unwrap_or(other_span),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -179,9 +174,9 @@ pub fn eval_hook(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block".to_string(),
|
||||
value: format!("{}", condition.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::Closure,
|
||||
actual: condition.get_type(),
|
||||
span: other_span,
|
||||
});
|
||||
}
|
||||
@ -224,11 +219,12 @@ pub fn eval_hook(
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
value: "source code with syntax errors".into(),
|
||||
span: source_span,
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("Failed to run {hook_name} hook"),
|
||||
msg: "source code has errors".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -263,9 +259,9 @@ pub fn eval_hook(
|
||||
run_hook(engine_state, stack, val, input, arguments, source_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or string".to_string(),
|
||||
value: format!("{}", other.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string or closure"),
|
||||
actual: other.get_type(),
|
||||
span: source_span,
|
||||
});
|
||||
}
|
||||
@ -276,16 +272,15 @@ pub fn eval_hook(
|
||||
output = run_hook(engine_state, stack, val, input, arguments, span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "string, block, record, or list of commands".into(),
|
||||
value: format!("{}", other.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string, closure, record, or list"),
|
||||
actual: other.get_type(),
|
||||
span: other.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
engine_state.merge_env(stack)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
@ -1,30 +1,9 @@
|
||||
use nu_path::AbsolutePathBuf;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Range, ShellError, Span, Value,
|
||||
};
|
||||
use std::ops::Bound;
|
||||
|
||||
pub fn get_init_cwd() -> AbsolutePathBuf {
|
||||
std::env::current_dir()
|
||||
.ok()
|
||||
.and_then(|path| AbsolutePathBuf::try_from(path).ok())
|
||||
.or_else(|| {
|
||||
std::env::var("PWD")
|
||||
.ok()
|
||||
.and_then(|path| AbsolutePathBuf::try_from(path).ok())
|
||||
})
|
||||
.or_else(nu_path::home_dir)
|
||||
.expect("Failed to get current working directory")
|
||||
}
|
||||
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> AbsolutePathBuf {
|
||||
engine_state
|
||||
.cwd(Some(stack))
|
||||
.ok()
|
||||
.unwrap_or_else(get_init_cwd)
|
||||
}
|
||||
|
||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||
|
||||
/// Returns a inclusive pair of boundary in given `range`.
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,13 +16,13 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-json = { version = "0.98.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-pretty-hex = { version = "0.98.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.99.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-json = { version = "0.99.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.99.0" }
|
||||
nu-pretty-hex = { version = "0.99.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.99.0" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
@ -36,6 +36,6 @@ v_htmlescape = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.98.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.99.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.99.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.99.0" }
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -15,16 +15,16 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.99.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.99.0" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.34", default-features = false }
|
||||
shadow-rs = { version = "0.35", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.34", default-features = false }
|
||||
shadow-rs = { version = "0.35", default-features = false }
|
||||
|
||||
[features]
|
||||
mimalloc = []
|
||||
|
@ -147,6 +147,7 @@ impl Command for Do {
|
||||
None
|
||||
};
|
||||
|
||||
child.ignore_error(false);
|
||||
child.wait()?;
|
||||
|
||||
let mut child = ChildProcess::from_raw(None, None, None, span);
|
||||
@ -166,10 +167,13 @@ impl Command for Do {
|
||||
}
|
||||
Ok(PipelineData::ByteStream(mut stream, metadata))
|
||||
if ignore_program_errors
|
||||
&& !matches!(caller_stack.stdout(), OutDest::Pipe | OutDest::Capture) =>
|
||||
&& !matches!(
|
||||
caller_stack.stdout(),
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
|
||||
) =>
|
||||
{
|
||||
if let ByteStreamSource::Child(child) = stream.source_mut() {
|
||||
child.set_exit_code(0)
|
||||
child.ignore_error(true);
|
||||
}
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ impl Command for HideEnv {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Hide an environment variable",
|
||||
example: r#"$env.HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
||||
example: r#"$env.HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in $env"#,
|
||||
result: Some(Value::test_bool(false)),
|
||||
}]
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{engine::StateWorkingSet, OutDest};
|
||||
use nu_protocol::{engine::StateWorkingSet, ByteStreamSource, OutDest};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ignore;
|
||||
@ -32,8 +32,13 @@ impl Command for Ignore {
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
input: PipelineData,
|
||||
mut input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let PipelineData::ByteStream(stream, _) = &mut input {
|
||||
if let ByteStreamSource::Child(child) = stream.source_mut() {
|
||||
child.ignore_error(true);
|
||||
}
|
||||
}
|
||||
input.drain()?;
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ impl Command for Let {
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
let stack = &mut stack.start_capture();
|
||||
let stack = &mut stack.start_collect_value();
|
||||
let pipeline_data = eval_block(engine_state, stack, block, input)?;
|
||||
let value = pipeline_data.into_value(call.head)?;
|
||||
|
||||
|
@ -63,7 +63,7 @@ impl Command for Mut {
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
let stack = &mut stack.start_capture();
|
||||
let stack = &mut stack.start_collect_value();
|
||||
let pipeline_data = eval_block(engine_state, stack, block, input)?;
|
||||
let value = pipeline_data.into_value(call.head)?;
|
||||
|
||||
|
@ -2,7 +2,7 @@ use nu_engine::{
|
||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block, redirect_env,
|
||||
};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::{ast::Expr, engine::CommandType};
|
||||
use nu_protocol::{ast::Expr, engine::CommandType, ModuleId};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@ -64,7 +64,7 @@ impl Command for OverlayUse {
|
||||
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||
|
||||
let maybe_origin_module_id =
|
||||
let maybe_origin_module_id: Option<ModuleId> =
|
||||
if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") {
|
||||
if let Expr::Overlay(module_id) = &overlay_expr.expr {
|
||||
*module_id
|
||||
|
@ -63,7 +63,7 @@ impl Command for Try {
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
|
||||
let result = eval_block(engine_state, stack, try_block, input)
|
||||
.and_then(|pipeline| pipeline.write_to_out_dests(engine_state, stack));
|
||||
.and_then(|pipeline| pipeline.drain_to_out_dests(engine_state, stack));
|
||||
|
||||
match result {
|
||||
Err(err) => run_catch(err, head, catch_block, engine_state, stack, eval_block),
|
||||
|
@ -126,7 +126,7 @@ pub fn eval_block(
|
||||
cwd: &std::path::Path,
|
||||
engine_state: &EngineState,
|
||||
) -> Value {
|
||||
let mut stack = Stack::new().capture();
|
||||
let mut stack = Stack::new().collect_value();
|
||||
|
||||
stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy()));
|
||||
|
||||
@ -143,13 +143,13 @@ pub fn check_example_evaluates_to_expected_output(
|
||||
cwd: &std::path::Path,
|
||||
engine_state: &mut Box<EngineState>,
|
||||
) {
|
||||
let mut stack = Stack::new().capture();
|
||||
let mut stack = Stack::new().collect_value();
|
||||
|
||||
// Set up PWD
|
||||
stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy()));
|
||||
|
||||
engine_state
|
||||
.merge_env(&mut stack, cwd)
|
||||
.merge_env(&mut stack)
|
||||
.expect("Error merging environment");
|
||||
|
||||
let empty_input = PipelineData::empty();
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-plugin"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.98.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.99.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.99.0" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
use crate::util::{get_plugin_dirs, modify_plugin_file};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
|
||||
use nu_protocol::{PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::util::{get_plugin_dirs, modify_plugin_file};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PluginAdd;
|
||||
|
||||
@ -31,7 +30,7 @@ impl Command for PluginAdd {
|
||||
)
|
||||
.required(
|
||||
"filename",
|
||||
SyntaxShape::Filepath,
|
||||
SyntaxShape::String,
|
||||
"Path to the executable for the plugin",
|
||||
)
|
||||
.category(Category::Plugin)
|
||||
@ -81,7 +80,6 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let filename: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let shell: Option<Spanned<String>> = call.get_flag(engine_state, stack, "shell")?;
|
||||
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
// Check the current directory, or fall back to NU_PLUGIN_DIRS
|
||||
|
@ -95,8 +95,9 @@ pub(crate) fn get_plugin_dirs(
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
let value = working_set
|
||||
.find_variable(b"$NU_PLUGIN_DIRS")
|
||||
.and_then(|var_id| working_set.get_constant(var_id).ok().cloned())
|
||||
.or_else(|| stack.get_env_var(engine_state, "NU_PLUGIN_DIRS"));
|
||||
.and_then(|var_id| working_set.get_constant(var_id).ok())
|
||||
.or_else(|| stack.get_env_var(engine_state, "NU_PLUGIN_DIRS"))
|
||||
.cloned(); // TODO: avoid this clone
|
||||
|
||||
// Get all of the strings in the list, if possible
|
||||
value
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,12 +14,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.99.0" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.99.0" }
|
@ -2,9 +2,8 @@ use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignm
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_engine::ClosureEvalOnce;
|
||||
use nu_protocol::{
|
||||
cli_error::CliError,
|
||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||
Span, Value,
|
||||
engine::{Closure, EngineState, Stack},
|
||||
report_shell_error, Span, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -70,14 +69,8 @@ impl<'a> StyleComputer<'a> {
|
||||
_ => Style::default(),
|
||||
}
|
||||
}
|
||||
// This is basically a copy of nu_cli::report_error(), but that isn't usable due to
|
||||
// dependencies. While crudely spitting out a bunch of errors like this is not ideal,
|
||||
// currently hook closure errors behave roughly the same.
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Error: {:?}",
|
||||
CliError(&e, &StateWorkingSet::new(self.engine_state))
|
||||
);
|
||||
Err(err) => {
|
||||
report_shell_error(self.engine_state, &err);
|
||||
Style::default()
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.98.0"
|
||||
version = "0.99.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,21 +16,21 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.98.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.98.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.98.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.98.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.99.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.99.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.99.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.99.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.99.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.99.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.99.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.99.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.99.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.99.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.99.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.99.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.99.0" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nuon = { path = "../nuon", version = "0.98.0" }
|
||||
nuon = { path = "../nuon", version = "0.99.0" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
@ -139,8 +139,8 @@ sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.99.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.99.0" }
|
||||
|
||||
dirs = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -183,6 +183,7 @@ mod test {
|
||||
use nu_protocol::{
|
||||
ast::{CellPath, PathMember},
|
||||
engine::Closure,
|
||||
BlockId,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
@ -244,7 +245,7 @@ mod test {
|
||||
Value::list(vec![Value::bool(true, span)], span),
|
||||
Value::closure(
|
||||
Closure {
|
||||
block_id: 0,
|
||||
block_id: BlockId::new(0),
|
||||
captures: Vec::new(),
|
||||
},
|
||||
span,
|
||||
|
@ -108,7 +108,7 @@ impl Command for Fill {
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Fill a filesize on the left side to a width of 5 with the character '0'",
|
||||
"Fill a filesize on both sides to a width of 10 with the character '0'",
|
||||
example: "1kib | fill --alignment middle --character '0' --width 10",
|
||||
result: Some(Value::string("0001024000", Span::test_data())),
|
||||
},
|
||||
|
@ -32,7 +32,11 @@ impl Command for IntoValue {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Infer nushell datatype for each cell."
|
||||
"Infer Nushell datatype for each cell."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "conversion"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -271,7 +271,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
||||
.join(" ")
|
||||
),
|
||||
//TODO: It would be good to drill deeper into closures.
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id),
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id.get()),
|
||||
Value::Nothing { .. } => String::new(),
|
||||
Value::Error { error, .. } => format!("{error:?}"),
|
||||
Value::Binary { val, .. } => format!("{val:?}"),
|
||||
|
@ -152,7 +152,7 @@ fn truncate_data(
|
||||
let left_space = expected_width - width;
|
||||
let has_space_for_truncation_column = left_space > PAD;
|
||||
if !has_space_for_truncation_column {
|
||||
peak_count -= 1;
|
||||
peak_count = peak_count.saturating_sub(1);
|
||||
}
|
||||
|
||||
remove_columns(data, peak_count);
|
||||
@ -201,11 +201,18 @@ mod util {
|
||||
Value::Record { val: record, .. } => {
|
||||
let (cols, vals): (Vec<_>, Vec<_>) = record.into_owned().into_iter().unzip();
|
||||
(
|
||||
cols,
|
||||
vec![vals
|
||||
match cols.is_empty() {
|
||||
true => vec![String::from("")],
|
||||
false => cols,
|
||||
},
|
||||
match vals
|
||||
.into_iter()
|
||||
.map(|s| debug_string_without_formatting(&s))
|
||||
.collect()],
|
||||
.collect::<Vec<String>>()
|
||||
{
|
||||
vals if vals.is_empty() => vec![],
|
||||
vals => vec![vals],
|
||||
},
|
||||
)
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{BlockId, DeclId};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ViewIr;
|
||||
@ -86,7 +87,8 @@ the declaration may not be in scope.
|
||||
let decl_id = val
|
||||
.try_into()
|
||||
.ok()
|
||||
.filter(|id| *id < engine_state.num_decls())
|
||||
.map(DeclId::new)
|
||||
.filter(|id| id.get() < engine_state.num_decls())
|
||||
.ok_or_else(|| ShellError::IncorrectValue {
|
||||
msg: "not a valid decl id".into(),
|
||||
val_span: target.span(),
|
||||
@ -102,11 +104,15 @@ the declaration may not be in scope.
|
||||
})?
|
||||
}
|
||||
// Block by ID - often shows up in IR
|
||||
Value::Int { val, .. } => val.try_into().map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "not a valid block id".into(),
|
||||
val_span: target.span(),
|
||||
call_span: call.head,
|
||||
})?,
|
||||
Value::Int { val, .. } => {
|
||||
val.try_into()
|
||||
.map(BlockId::new)
|
||||
.map_err(|_| ShellError::IncorrectValue {
|
||||
msg: "not a valid block id".into(),
|
||||
val_span: target.span(),
|
||||
call_span: call.head,
|
||||
})?
|
||||
}
|
||||
// Pass through errors
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
_ => {
|
||||
@ -119,7 +125,7 @@ the declaration may not be in scope.
|
||||
|
||||
let Some(block) = engine_state.try_get_block(block_id) else {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("Unknown block ID: {block_id}"),
|
||||
error: format!("Unknown block ID: {}", block_id.get()),
|
||||
msg: "ensure the block ID is correct and try again".into(),
|
||||
span: Some(target.span()),
|
||||
help: None,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Config;
|
||||
use nu_protocol::{Config, DataSource, PipelineMetadata};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
@ -32,7 +32,7 @@ impl Command for ViewSource {
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
let arg_span = arg.span();
|
||||
|
||||
match arg {
|
||||
let source = match arg {
|
||||
Value::String { val, .. } => {
|
||||
if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
|
||||
// arg is a command
|
||||
@ -193,7 +193,13 @@ impl Command for ViewSource {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
source.map(|x| {
|
||||
x.set_metadata(Some(PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("application/x-nuscript".into()),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -46,7 +46,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
First,
|
||||
Flatten,
|
||||
Get,
|
||||
Group,
|
||||
GroupBy,
|
||||
Headers,
|
||||
Insert,
|
||||
@ -188,8 +187,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
EncodeBase32Hex,
|
||||
DecodeBase64,
|
||||
EncodeBase64,
|
||||
DecodeBase64Old,
|
||||
EncodeBase64Old,
|
||||
DetectColumns,
|
||||
Parse,
|
||||
Split,
|
||||
|
@ -45,13 +45,9 @@ impl Command for ConfigReset {
|
||||
let only_env = call.has_flag(engine_state, stack, "env")?;
|
||||
let no_backup = call.has_flag(engine_state, stack, "without-backup")?;
|
||||
let span = call.head;
|
||||
let mut config_path = match nu_path::config_dir() {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
return Err(ShellError::ConfigDirNotFound { span: None });
|
||||
}
|
||||
let Some(config_path) = nu_path::nu_config_dir() else {
|
||||
return Err(ShellError::ConfigDirNotFound { span: None });
|
||||
};
|
||||
config_path.push("nushell");
|
||||
if !only_env {
|
||||
let mut nu_config = config_path.clone();
|
||||
nu_config.push("config.nu");
|
||||
|
5
crates/nu-command/src/env/source_env.rs
vendored
5
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,
|
||||
redirect_env,
|
||||
};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use nu_protocol::{engine::CommandType, BlockId};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Source a file for environment variables.
|
||||
@ -50,6 +50,7 @@ impl Command for SourceEnv {
|
||||
// Note: this hidden positional is the block_id that corresponded to the 0th position
|
||||
// it is put here by the parser
|
||||
let block_id: i64 = call.req_parser_info(engine_state, caller_stack, "block_id")?;
|
||||
let block_id = BlockId::new(block_id as usize);
|
||||
|
||||
// Set the currently evaluated directory (file-relative PWD)
|
||||
let file_path = if let Some(path) = find_in_dirs_env(
|
||||
@ -78,7 +79,7 @@ impl Command for SourceEnv {
|
||||
);
|
||||
|
||||
// Evaluate the block
|
||||
let block = engine_state.get_block(block_id as usize).clone();
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
let mut callee_stack = caller_stack
|
||||
.gather_captures(engine_state, &block.captures)
|
||||
.reset_pipes();
|
||||
|
@ -1,4 +1,3 @@
|
||||
use nu_cmd_base::util::get_init_cwd;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||
|
||||
@ -41,12 +40,14 @@ impl Command for Cd {
|
||||
let physical = call.has_flag(engine_state, stack, "physical")?;
|
||||
let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
// If getting PWD failed, default to the initial directory. This way, the
|
||||
// user can use `cd` to recover PWD to a good state.
|
||||
// If getting PWD failed, default to the home directory. The user can
|
||||
// use `cd` to reset PWD to a good state.
|
||||
let cwd = engine_state
|
||||
.cwd(Some(stack))
|
||||
.ok()
|
||||
.unwrap_or_else(get_init_cwd);
|
||||
.or_else(nu_path::home_dir)
|
||||
.map(|path| path.into_std_path_buf())
|
||||
.unwrap_or_default();
|
||||
|
||||
let path_val = {
|
||||
if let Some(path) = path_val {
|
||||
@ -65,7 +66,7 @@ impl Command for Cd {
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
|
||||
oldpwd.to_path()?
|
||||
} else {
|
||||
cwd.into()
|
||||
cwd
|
||||
}
|
||||
} else {
|
||||
// Trim whitespace from the end of path.
|
||||
@ -106,7 +107,7 @@ impl Command for Cd {
|
||||
// Set OLDPWD.
|
||||
// We're using `Stack::get_env_var()` instead of `EngineState::cwd()` to avoid a conversion roundtrip.
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
stack.add_env_var("OLDPWD".into(), oldpwd)
|
||||
stack.add_env_var("OLDPWD".into(), oldpwd.clone())
|
||||
}
|
||||
|
||||
match have_permission(&path) {
|
||||
|
@ -169,10 +169,16 @@ impl Command for Glob {
|
||||
});
|
||||
}
|
||||
|
||||
// below we have to check / instead of MAIN_SEPARATOR because glob uses / as separator
|
||||
// using a glob like **\*.rs should fail because it's not a valid glob pattern
|
||||
let folder_depth = if let Some(depth) = depth {
|
||||
depth
|
||||
} else {
|
||||
} else if glob_pattern.contains("**") {
|
||||
usize::MAX
|
||||
} else if glob_pattern.contains('/') {
|
||||
glob_pattern.split('/').count() + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let (prefix, glob) = match WaxGlob::new(&glob_pattern) {
|
||||
|
@ -8,11 +8,14 @@ use nu_glob::MatchOptions;
|
||||
use nu_path::{expand_path_with, expand_to_real_path};
|
||||
use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals};
|
||||
use pathdiff::diff_paths;
|
||||
use rayon::prelude::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc,
|
||||
sync::{Arc, Mutex},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
@ -28,6 +31,7 @@ struct Args {
|
||||
du: bool,
|
||||
directory: bool,
|
||||
use_mime_type: bool,
|
||||
use_threads: bool,
|
||||
call_span: Span,
|
||||
}
|
||||
|
||||
@ -75,6 +79,7 @@ impl Command for Ls {
|
||||
Some('D'),
|
||||
)
|
||||
.switch("mime-type", "Show mime-type in type column instead of 'file' (based on filenames only; files' contents are not examined)", Some('m'))
|
||||
.switch("threads", "Use multiple threads to list contents. Output will be non-deterministic.", Some('t'))
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
@ -92,6 +97,7 @@ impl Command for Ls {
|
||||
let du = call.has_flag(engine_state, stack, "du")?;
|
||||
let directory = call.has_flag(engine_state, stack, "directory")?;
|
||||
let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?;
|
||||
let use_threads = call.has_flag(engine_state, stack, "threads")?;
|
||||
let call_span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
@ -104,6 +110,7 @@ impl Command for Ls {
|
||||
du,
|
||||
directory,
|
||||
use_mime_type,
|
||||
use_threads,
|
||||
call_span,
|
||||
};
|
||||
|
||||
@ -114,22 +121,24 @@ impl Command for Ls {
|
||||
Some(pattern_arg)
|
||||
};
|
||||
match input_pattern_arg {
|
||||
None => Ok(ls_for_one_pattern(None, args, engine_state.signals(), cwd)?
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
content_type: None,
|
||||
},
|
||||
)),
|
||||
None => Ok(
|
||||
ls_for_one_pattern(None, args, engine_state.signals().clone(), cwd)?
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
content_type: None,
|
||||
},
|
||||
),
|
||||
),
|
||||
Some(pattern) => {
|
||||
let mut result_iters = vec![];
|
||||
for pat in pattern {
|
||||
result_iters.push(ls_for_one_pattern(
|
||||
Some(pat),
|
||||
args,
|
||||
engine_state.signals(),
|
||||
engine_state.signals().clone(),
|
||||
cwd.clone(),
|
||||
)?)
|
||||
}
|
||||
@ -213,9 +222,27 @@ impl Command for Ls {
|
||||
fn ls_for_one_pattern(
|
||||
pattern_arg: Option<Spanned<NuGlob>>,
|
||||
args: Args,
|
||||
signals: &Signals,
|
||||
signals: Signals,
|
||||
cwd: PathBuf,
|
||||
) -> Result<Box<dyn Iterator<Item = Value> + Send>, ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
fn create_pool(num_threads: usize) -> Result<rayon::ThreadPool, ShellError> {
|
||||
match rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(num_threads)
|
||||
.build()
|
||||
{
|
||||
Err(e) => Err(e).map_err(|e| ShellError::GenericError {
|
||||
error: "Error creating thread pool".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::unknown()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
}),
|
||||
Ok(pool) => Ok(pool),
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let Args {
|
||||
all,
|
||||
long,
|
||||
@ -224,6 +251,7 @@ fn ls_for_one_pattern(
|
||||
du,
|
||||
directory,
|
||||
use_mime_type,
|
||||
use_threads,
|
||||
call_span,
|
||||
} = args;
|
||||
let pattern_arg = {
|
||||
@ -281,7 +309,7 @@ fn ls_for_one_pattern(
|
||||
});
|
||||
}
|
||||
if is_empty_dir(&tmp_expanded) {
|
||||
return Ok(Box::new(vec![].into_iter()));
|
||||
return Ok(Value::test_nothing().into_pipeline_data());
|
||||
}
|
||||
just_read_dir = !(pat.item.is_expand() && pat.item.as_ref().contains(GLOB_CHARS));
|
||||
}
|
||||
@ -300,7 +328,7 @@ fn ls_for_one_pattern(
|
||||
if directory {
|
||||
(NuGlob::Expand(".".to_string()), false)
|
||||
} else if is_empty_dir(&cwd) {
|
||||
return Ok(Box::new(vec![].into_iter()));
|
||||
return Ok(Value::test_nothing().into_pipeline_data());
|
||||
} else {
|
||||
(NuGlob::Expand("*".to_string()), false)
|
||||
}
|
||||
@ -338,92 +366,130 @@ fn ls_for_one_pattern(
|
||||
});
|
||||
}
|
||||
|
||||
let mut hidden_dirs = vec![];
|
||||
let hidden_dirs = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let signals = signals.clone();
|
||||
Ok(Box::new(paths_peek.filter_map(move |x| match x {
|
||||
Ok(path) => {
|
||||
let metadata = match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) => Some(metadata),
|
||||
Err(_) => None,
|
||||
};
|
||||
if path_contains_hidden_folder(&path, &hidden_dirs) {
|
||||
return None;
|
||||
}
|
||||
let signals_clone = signals.clone();
|
||||
|
||||
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
|
||||
if path.is_dir() {
|
||||
hidden_dirs.push(path);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let pool = if use_threads {
|
||||
let count = std::thread::available_parallelism()?.get();
|
||||
create_pool(count)?
|
||||
} else {
|
||||
create_pool(1)?
|
||||
};
|
||||
|
||||
let display_name = if short_names {
|
||||
path.file_name().map(|os| os.to_string_lossy().to_string())
|
||||
} else if full_paths || absolute_path {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
} else if let Some(prefix) = &prefix {
|
||||
if let Ok(remainder) = path.strip_prefix(prefix) {
|
||||
if directory {
|
||||
// When the path is the same as the cwd, path_diff should be "."
|
||||
let path_diff = if let Some(path_diff_not_dot) = diff_paths(&path, &cwd) {
|
||||
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
||||
if path_diff_not_dot.is_empty() {
|
||||
".".to_string()
|
||||
pool.install(|| {
|
||||
paths_peek
|
||||
.par_bridge()
|
||||
.filter_map(move |x| match x {
|
||||
Ok(path) => {
|
||||
let metadata = match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) => Some(metadata),
|
||||
Err(_) => None,
|
||||
};
|
||||
let hidden_dir_clone = Arc::clone(&hidden_dirs);
|
||||
let mut hidden_dir_mutex = hidden_dir_clone
|
||||
.lock()
|
||||
.expect("Unable to acquire lock for hidden_dirs");
|
||||
if path_contains_hidden_folder(&path, &hidden_dir_mutex) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
|
||||
if path.is_dir() {
|
||||
hidden_dir_mutex.push(path);
|
||||
drop(hidden_dir_mutex);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
let display_name = if short_names {
|
||||
path.file_name().map(|os| os.to_string_lossy().to_string())
|
||||
} else if full_paths || absolute_path {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
} else if let Some(prefix) = &prefix {
|
||||
if let Ok(remainder) = path.strip_prefix(prefix) {
|
||||
if directory {
|
||||
// When the path is the same as the cwd, path_diff should be "."
|
||||
let path_diff =
|
||||
if let Some(path_diff_not_dot) = diff_paths(&path, &cwd) {
|
||||
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
||||
if path_diff_not_dot.is_empty() {
|
||||
".".to_string()
|
||||
} else {
|
||||
path_diff_not_dot.to_string()
|
||||
}
|
||||
} else {
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
Some(path_diff)
|
||||
} else {
|
||||
path_diff_not_dot.to_string()
|
||||
let new_prefix = if let Some(pfx) = diff_paths(prefix, &cwd) {
|
||||
pfx
|
||||
} else {
|
||||
prefix.to_path_buf()
|
||||
};
|
||||
|
||||
Some(new_prefix.join(remainder).to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
Some(path_diff)
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
let new_prefix = if let Some(pfx) = diff_paths(prefix, &cwd) {
|
||||
pfx
|
||||
} else {
|
||||
prefix.to_path_buf()
|
||||
};
|
||||
|
||||
Some(new_prefix.join(remainder).to_string_lossy().to_string())
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
.ok_or_else(|| ShellError::GenericError {
|
||||
error: format!("Invalid file name: {:}", path.to_string_lossy()),
|
||||
msg: "invalid file name".into(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
.ok_or_else(|| ShellError::GenericError {
|
||||
error: format!("Invalid file name: {:}", path.to_string_lossy()),
|
||||
msg: "invalid file name".into(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
|
||||
match display_name {
|
||||
Ok(name) => {
|
||||
let entry = dir_entry_dict(
|
||||
&path,
|
||||
&name,
|
||||
metadata.as_ref(),
|
||||
call_span,
|
||||
long,
|
||||
du,
|
||||
&signals,
|
||||
use_mime_type,
|
||||
args.full_paths,
|
||||
);
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
match display_name {
|
||||
Ok(name) => {
|
||||
let entry = dir_entry_dict(
|
||||
&path,
|
||||
&name,
|
||||
metadata.as_ref(),
|
||||
call_span,
|
||||
long,
|
||||
du,
|
||||
&signals_clone,
|
||||
use_mime_type,
|
||||
args.full_paths,
|
||||
);
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
})))
|
||||
})
|
||||
.try_for_each(|stream| {
|
||||
tx.send(stream).map_err(|e| ShellError::GenericError {
|
||||
error: "Error streaming data".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
})
|
||||
})
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Unable to create a rayon pool".into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
|
||||
Ok(rx
|
||||
.into_iter()
|
||||
.into_pipeline_data(call_span, signals.clone()))
|
||||
}
|
||||
|
||||
fn permission_denied(dir: impl AsRef<Path>) -> bool {
|
||||
|
@ -12,6 +12,7 @@ use std::{
|
||||
io::{self, BufRead, BufReader, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -182,6 +183,8 @@ impl Command for Save {
|
||||
}
|
||||
(None, None) => {}
|
||||
};
|
||||
|
||||
child.wait()?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +273,7 @@ impl Command for Save {
|
||||
}
|
||||
|
||||
fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
|
||||
(Some(OutDest::Capture), Some(OutDest::Capture))
|
||||
(Some(OutDest::PipeSeparate), Some(OutDest::PipeSeparate))
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +483,7 @@ fn stream_to_file(
|
||||
|
||||
let mut bar = progress_bar::NuProgressBar::new(known_size);
|
||||
|
||||
// TODO: reduce the number of progress bar updates?
|
||||
let mut last_update = Instant::now();
|
||||
|
||||
let mut reader = BufReader::new(source);
|
||||
|
||||
@ -497,7 +500,10 @@ fn stream_to_file(
|
||||
let len = buf.len();
|
||||
reader.consume(len);
|
||||
bytes_processed += len as u64;
|
||||
bar.update_bar(bytes_processed);
|
||||
if last_update.elapsed() >= Duration::from_millis(75) {
|
||||
bar.update_bar(bytes_processed);
|
||||
last_update = Instant::now();
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => break Err(e),
|
||||
|
@ -58,9 +58,8 @@ impl Command for Start {
|
||||
open_path(url.as_str(), engine_state, stack, path.span)?;
|
||||
} else {
|
||||
// try to distinguish between file not found and opening url without prefix
|
||||
if let Ok(canon_path) =
|
||||
canonicalize_with(path_no_whitespace, std::env::current_dir()?.as_path())
|
||||
{
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
if let Ok(canon_path) = canonicalize_with(path_no_whitespace, cwd) {
|
||||
open_path(canon_path, engine_state, stack, path.span)?;
|
||||
} else {
|
||||
// open crate does not allow opening URL without prefix
|
||||
|
@ -1,132 +0,0 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{report_shell_warning, ValueIterator};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Group;
|
||||
|
||||
impl Command for Group {
|
||||
fn name(&self) -> &str {
|
||||
"group"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("group")
|
||||
// TODO: It accepts Table also, but currently there is no Table
|
||||
// example. Perhaps Table should be a subtype of List, in which case
|
||||
// the current signature would suffice even when a Table example
|
||||
// exists.
|
||||
.input_output_types(vec![(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::List(Box::new(Type::Any)))),
|
||||
)])
|
||||
.required("group_size", SyntaxShape::Int, "The size of each group.")
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Groups input into groups of `group_size`."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let stream_test_1 = vec![
|
||||
Value::list(
|
||||
vec![Value::test_int(1), Value::test_int(2)],
|
||||
Span::test_data(),
|
||||
),
|
||||
Value::list(
|
||||
vec![Value::test_int(3), Value::test_int(4)],
|
||||
Span::test_data(),
|
||||
),
|
||||
];
|
||||
|
||||
vec![Example {
|
||||
example: "[1 2 3 4] | group 2",
|
||||
description: "Group the a list by pairs",
|
||||
result: Some(Value::list(stream_test_1, Span::test_data())),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
report_shell_warning(
|
||||
engine_state,
|
||||
&ShellError::Deprecated {
|
||||
old_command: "group".into(),
|
||||
new_suggestion: "the new `chunks` command".into(),
|
||||
span: head,
|
||||
url: "`help chunks`".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let each_group_iterator = EachGroupIterator {
|
||||
group_size: group_size.item,
|
||||
input: Box::new(input.into_iter()),
|
||||
span: head,
|
||||
};
|
||||
|
||||
Ok(each_group_iterator.into_pipeline_data_with_metadata(
|
||||
head,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct EachGroupIterator {
|
||||
group_size: usize,
|
||||
input: ValueIterator,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl Iterator for EachGroupIterator {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut group = vec![];
|
||||
let mut current_count = 0;
|
||||
|
||||
loop {
|
||||
let item = self.input.next();
|
||||
|
||||
match item {
|
||||
Some(v) => {
|
||||
group.push(v);
|
||||
|
||||
current_count += 1;
|
||||
if current_count >= self.group_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
if group.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Value::list(group, self.span))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Group {})
|
||||
}
|
||||
}
|
@ -38,21 +38,21 @@ repeating this process with row 1, and so on."#
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "[a b c] | wrap name | merge ( [1 2 3] | wrap index )",
|
||||
description: "Add an 'index' column to the input table",
|
||||
example: "[a b c] | wrap name | merge ( [47 512 618] | wrap id )",
|
||||
description: "Add an 'id' column to the input table",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_record(record! {
|
||||
"name" => Value::test_string("a"),
|
||||
"index" => Value::test_int(1),
|
||||
"id" => Value::test_int(47),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"name" => Value::test_string("b"),
|
||||
"index" => Value::test_int(2),
|
||||
"id" => Value::test_int(512),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"name" => Value::test_string("c"),
|
||||
"index" => Value::test_int(3),
|
||||
"id" => Value::test_int(618),
|
||||
}),
|
||||
],
|
||||
Span::test_data(),
|
||||
|
@ -15,7 +15,6 @@ mod find;
|
||||
mod first;
|
||||
mod flatten;
|
||||
mod get;
|
||||
mod group;
|
||||
mod group_by;
|
||||
mod headers;
|
||||
mod insert;
|
||||
@ -73,7 +72,6 @@ pub use find::Find;
|
||||
pub use first::First;
|
||||
pub use flatten::Flatten;
|
||||
pub use get::Get;
|
||||
pub use group::Group;
|
||||
pub use group_by::GroupBy;
|
||||
pub use headers::Headers;
|
||||
pub use insert::Insert;
|
||||
|
@ -1,8 +1,7 @@
|
||||
use alphanumeric_sort::compare_str;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ast::PathMember;
|
||||
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use std::cmp::Ordering;
|
||||
use crate::Comparator;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sort;
|
||||
@ -14,10 +13,13 @@ impl Command for Sort {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("sort")
|
||||
.input_output_types(vec![(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
), (Type::record(), Type::record()),])
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any))
|
||||
),
|
||||
(Type::record(), Type::record())
|
||||
])
|
||||
.switch("reverse", "Sort in reverse order", Some('r'))
|
||||
.switch(
|
||||
"ignore-case",
|
||||
@ -45,67 +47,66 @@ impl Command for Sort {
|
||||
vec![
|
||||
Example {
|
||||
example: "[2 0 1] | sort",
|
||||
description: "sort the list by increasing value",
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
|
||||
Span::test_data(),
|
||||
)),
|
||||
description: "Sort the list by increasing value",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(0),
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
example: "[2 0 1] | sort --reverse",
|
||||
description: "sort the list by decreasing value",
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(2), Value::test_int(1), Value::test_int(0)],
|
||||
Span::test_data(),
|
||||
)),
|
||||
description: "Sort the list by decreasing value",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(2),
|
||||
Value::test_int(1),
|
||||
Value::test_int(0),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
example: "[betty amy sarah] | sort",
|
||||
description: "sort a list of strings",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("amy"),
|
||||
Value::test_string("betty"),
|
||||
Value::test_string("sarah"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
description: "Sort a list of strings",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_string("amy"),
|
||||
Value::test_string("betty"),
|
||||
Value::test_string("sarah"),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
example: "[betty amy sarah] | sort --reverse",
|
||||
description: "sort a list of strings in reverse",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("sarah"),
|
||||
Value::test_string("betty"),
|
||||
Value::test_string("amy"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
description: "Sort a list of strings in reverse",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_string("sarah"),
|
||||
Value::test_string("betty"),
|
||||
Value::test_string("amy"),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort strings (case-insensitive)",
|
||||
example: "[airplane Truck Car] | sort -i",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("airplane"),
|
||||
Value::test_string("Car"),
|
||||
Value::test_string("Truck"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_string("airplane"),
|
||||
Value::test_string("Car"),
|
||||
Value::test_string("Truck"),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort strings (reversed case-insensitive)",
|
||||
example: "[airplane Truck Car] | sort -i -r",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("Truck"),
|
||||
Value::test_string("Car"),
|
||||
Value::test_string("airplane"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_string("Truck"),
|
||||
Value::test_string("Car"),
|
||||
Value::test_string("airplane"),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort alphanumeric strings in natural order",
|
||||
example: "[foo1 foo10 foo9] | sort -n",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_string("foo1"),
|
||||
Value::test_string("foo9"),
|
||||
Value::test_string("foo10"),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort record by key (case-insensitive)",
|
||||
@ -134,233 +135,65 @@ impl Command for Sort {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let reverse = call.has_flag(engine_state, stack, "reverse")?;
|
||||
let insensitive = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
let natural = call.has_flag(engine_state, stack, "natural")?;
|
||||
let sort_by_value = call.has_flag(engine_state, stack, "values")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
let span = input.span().unwrap_or(call.head);
|
||||
match input {
|
||||
// Records have two sorting methods, toggled by presence or absence of -v
|
||||
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
||||
let sort_by_value = call.has_flag(engine_state, stack, "values")?;
|
||||
let record = sort_record(
|
||||
let value = input.into_value(span)?;
|
||||
let sorted: Value = match value {
|
||||
Value::Record { val, .. } => {
|
||||
// Records have two sorting methods, toggled by presence or absence of -v
|
||||
let record = crate::sort_record(
|
||||
val.into_owned(),
|
||||
span,
|
||||
sort_by_value,
|
||||
reverse,
|
||||
insensitive,
|
||||
natural,
|
||||
);
|
||||
Ok(record.into_pipeline_data())
|
||||
)?;
|
||||
Value::record(record, span)
|
||||
}
|
||||
// Other values are sorted here
|
||||
PipelineData::Value(v, ..)
|
||||
if !matches!(v, Value::List { .. } | Value::Range { .. }) =>
|
||||
{
|
||||
Ok(v.into_pipeline_data())
|
||||
}
|
||||
pipe_data => {
|
||||
let mut vec: Vec<_> = pipe_data.into_iter().collect();
|
||||
|
||||
sort(&mut vec, head, insensitive, natural)?;
|
||||
value @ Value::List { .. } => {
|
||||
// If we have a table specifically, then we want to sort along each column.
|
||||
// Record's PartialOrd impl dictates that columns are compared in alphabetical order,
|
||||
// so we have to explicitly compare by each column.
|
||||
let r#type = value.get_type();
|
||||
let mut vec = value.into_list().expect("matched list above");
|
||||
if let Type::Table(cols) = r#type {
|
||||
let columns: Vec<Comparator> = cols
|
||||
.iter()
|
||||
.map(|col| vec![PathMember::string(col.0.clone(), false, Span::unknown())])
|
||||
.map(|members| CellPath { members })
|
||||
.map(Comparator::CellPath)
|
||||
.collect();
|
||||
crate::sort_by(&mut vec, columns, span, insensitive, natural)?;
|
||||
} else {
|
||||
crate::sort(&mut vec, insensitive, natural)?;
|
||||
}
|
||||
|
||||
if reverse {
|
||||
vec.reverse()
|
||||
}
|
||||
|
||||
let iter = vec.into_iter();
|
||||
Ok(iter.into_pipeline_data_with_metadata(
|
||||
head,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
))
|
||||
Value::list(vec, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_record(
|
||||
record: Record,
|
||||
rec_span: Span,
|
||||
sort_by_value: bool,
|
||||
reverse: bool,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Value {
|
||||
let mut input_pairs: Vec<(String, Value)> = record.into_iter().collect();
|
||||
input_pairs.sort_by(|a, b| {
|
||||
// Extract the data (if sort_by_value) or the column names for comparison
|
||||
let left_res = if sort_by_value {
|
||||
match &a.1 {
|
||||
Value::String { val, .. } => val.clone(),
|
||||
val => {
|
||||
if let Ok(val) = val.coerce_string() {
|
||||
val
|
||||
} else {
|
||||
// Values that can't be turned to strings are disregarded by the sort
|
||||
// (same as in sort_utils.rs)
|
||||
return Ordering::Equal;
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } => {
|
||||
return Err(ShellError::PipelineEmpty {
|
||||
dst_span: value.span(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
a.0.clone()
|
||||
};
|
||||
let right_res = if sort_by_value {
|
||||
match &b.1 {
|
||||
Value::String { val, .. } => val.clone(),
|
||||
val => {
|
||||
if let Ok(val) = val.coerce_string() {
|
||||
val
|
||||
} else {
|
||||
// Values that can't be turned to strings are disregarded by the sort
|
||||
// (same as in sort_utils.rs)
|
||||
return Ordering::Equal;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::PipelineMismatch {
|
||||
exp_input_type: "record or list".to_string(),
|
||||
dst_span: call.head,
|
||||
src_span: value.span(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
b.0.clone()
|
||||
};
|
||||
|
||||
// Fold case if case-insensitive
|
||||
let left = if insensitive {
|
||||
left_res.to_folded_case()
|
||||
} else {
|
||||
left_res
|
||||
};
|
||||
let right = if insensitive {
|
||||
right_res.to_folded_case()
|
||||
} else {
|
||||
right_res
|
||||
};
|
||||
|
||||
if natural {
|
||||
compare_str(left, right)
|
||||
} else {
|
||||
left.cmp(&right)
|
||||
}
|
||||
});
|
||||
|
||||
if reverse {
|
||||
input_pairs.reverse();
|
||||
Ok(sorted.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
|
||||
Value::record(input_pairs.into_iter().collect(), rec_span)
|
||||
}
|
||||
|
||||
pub fn sort(
|
||||
vec: &mut [Value],
|
||||
span: Span,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
match vec.first() {
|
||||
Some(Value::Record { val, .. }) => {
|
||||
let columns: Vec<String> = val.columns().cloned().collect();
|
||||
vec.sort_by(|a, b| process(a, b, &columns, span, insensitive, natural));
|
||||
}
|
||||
_ => {
|
||||
vec.sort_by(|a, b| {
|
||||
let span_a = a.span();
|
||||
let span_b = b.span();
|
||||
if insensitive {
|
||||
let folded_left = match a {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_a),
|
||||
_ => a.clone(),
|
||||
};
|
||||
|
||||
let folded_right = match b {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_b),
|
||||
_ => b.clone(),
|
||||
};
|
||||
|
||||
if natural {
|
||||
match (
|
||||
folded_left.coerce_into_string(),
|
||||
folded_right.coerce_into_string(),
|
||||
) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
folded_left
|
||||
.partial_cmp(&folded_right)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
}
|
||||
} else if natural {
|
||||
match (a.coerce_str(), b.coerce_str()) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
a.partial_cmp(b).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
columns: &[String],
|
||||
span: Span,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Ordering {
|
||||
for column in columns {
|
||||
let left_value = left.get_data_by_key(column);
|
||||
|
||||
let left_res = match left_value {
|
||||
Some(left_res) => left_res,
|
||||
None => Value::nothing(span),
|
||||
};
|
||||
|
||||
let right_value = right.get_data_by_key(column);
|
||||
|
||||
let right_res = match right_value {
|
||||
Some(right_res) => right_res,
|
||||
None => Value::nothing(span),
|
||||
};
|
||||
|
||||
let result = if insensitive {
|
||||
let span_left = left_res.span();
|
||||
let span_right = right_res.span();
|
||||
let folded_left = match left_res {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_left),
|
||||
_ => left_res,
|
||||
};
|
||||
|
||||
let folded_right = match right_res {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_right),
|
||||
_ => right_res,
|
||||
};
|
||||
if natural {
|
||||
match (
|
||||
folded_left.coerce_into_string(),
|
||||
folded_right.coerce_into_string(),
|
||||
) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
folded_left
|
||||
.partial_cmp(&folded_right)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
}
|
||||
} else {
|
||||
left_res.partial_cmp(&right_res).unwrap_or(Ordering::Equal)
|
||||
};
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,4 +1,6 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_engine::{command_prelude::*, ClosureEval};
|
||||
|
||||
use crate::Comparator;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SortBy;
|
||||
@ -18,24 +20,37 @@ impl Command for SortBy {
|
||||
(Type::record(), Type::table()),
|
||||
(Type::table(), Type::table()),
|
||||
])
|
||||
.rest("columns", SyntaxShape::Any, "The column(s) to sort by.")
|
||||
.rest(
|
||||
"comparator",
|
||||
SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::CellPath,
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), // key closure
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])), // custom closure
|
||||
]),
|
||||
"The cell path(s) or closure(s) to compare elements by.",
|
||||
)
|
||||
.switch("reverse", "Sort in reverse order", Some('r'))
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"Sort string-based columns case-insensitively",
|
||||
"Sort string-based data case-insensitively",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"natural",
|
||||
"Sort alphanumeric string-based columns naturally (1, 9, 10, 99, 100, ...)",
|
||||
"Sort alphanumeric string-based data naturally (1, 9, 10, 99, 100, ...)",
|
||||
Some('n'),
|
||||
)
|
||||
.switch(
|
||||
"custom",
|
||||
"Use closures to specify a custom sort order, rather than to compute a comparison key",
|
||||
Some('c'),
|
||||
)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Sort by the given columns, in increasing order."
|
||||
"Sort by the given cell path or closure."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -68,6 +83,41 @@ impl Command for SortBy {
|
||||
}),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort by a nested value",
|
||||
example: "[[name info]; [Cairo {founded: 969}] [Kyoto {founded: 794}]] | sort-by info.founded",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"name" => Value::test_string("Kyoto"),
|
||||
"info" => Value::test_record(
|
||||
record! { "founded" => Value::test_int(794) },
|
||||
)}),
|
||||
Value::test_record(record! {
|
||||
"name" => Value::test_string("Cairo"),
|
||||
"info" => Value::test_record(
|
||||
record! { "founded" => Value::test_int(969) },
|
||||
)})
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Sort by the last value in a list",
|
||||
example: "[[2 50] [10 1]] | sort-by { last }",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_list(vec![Value::test_int(10), Value::test_int(1)]),
|
||||
Value::test_list(vec![Value::test_int(2), Value::test_int(50)])
|
||||
]))
|
||||
},
|
||||
Example {
|
||||
description: "Sort in a custom order",
|
||||
example: "[7 3 2 8 4] | sort-by -c {|a, b| $a < $b}",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(2),
|
||||
Value::test_int(3),
|
||||
Value::test_int(4),
|
||||
Value::test_int(7),
|
||||
Value::test_int(8),
|
||||
]))
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -79,39 +129,60 @@ impl Command for SortBy {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let columns: Vec<String> = call.rest(engine_state, stack, 0)?;
|
||||
let comparator_vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let reverse = call.has_flag(engine_state, stack, "reverse")?;
|
||||
let insensitive = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
let natural = call.has_flag(engine_state, stack, "natural")?;
|
||||
let custom = call.has_flag(engine_state, stack, "custom")?;
|
||||
let metadata = input.metadata();
|
||||
let mut vec: Vec<_> = input.into_iter_strict(head)?.collect();
|
||||
|
||||
if columns.is_empty() {
|
||||
if comparator_vals.is_empty() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "columns".into(),
|
||||
param_name: "comparator".into(),
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
crate::sort(&mut vec, columns, head, insensitive, natural)?;
|
||||
let comparators = comparator_vals
|
||||
.into_iter()
|
||||
.map(|val| match val {
|
||||
Value::CellPath { val, .. } => Ok(Comparator::CellPath(val)),
|
||||
Value::Closure { val, .. } => {
|
||||
let closure_eval = ClosureEval::new(engine_state, stack, *val);
|
||||
if custom {
|
||||
Ok(Comparator::CustomClosure(closure_eval))
|
||||
} else {
|
||||
Ok(Comparator::KeyClosure(closure_eval))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "Cannot sort using a value which is not a cell path or closure"
|
||||
.into(),
|
||||
span: val.span(),
|
||||
}),
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
crate::sort_by(&mut vec, comparators, head, insensitive, natural)?;
|
||||
|
||||
if reverse {
|
||||
vec.reverse()
|
||||
}
|
||||
|
||||
let iter = vec.into_iter();
|
||||
Ok(iter.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
|
||||
let val = Value::list(vec, head);
|
||||
Ok(val.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{test_examples_with_commands, Last};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SortBy {})
|
||||
test_examples_with_commands(SortBy {}, &[&Last]);
|
||||
}
|
||||
}
|
||||
|
@ -158,12 +158,12 @@ use it in your pipeline."#
|
||||
let tee_thread = spawn_tee(info.clone(), eval_block)?;
|
||||
let tee = IoTee::new(stderr, tee_thread);
|
||||
match stack.stderr() {
|
||||
OutDest::Pipe | OutDest::Capture => {
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => {
|
||||
child.stderr = Some(ChildPipe::Tee(Box::new(tee)));
|
||||
Ok(None)
|
||||
}
|
||||
OutDest::Null => copy_on_thread(tee, io::sink(), &info).map(Some),
|
||||
OutDest::Inherit => {
|
||||
OutDest::Print | OutDest::Inherit => {
|
||||
copy_on_thread(tee, io::stderr(), &info).map(Some)
|
||||
}
|
||||
OutDest::File(file) => {
|
||||
@ -176,12 +176,14 @@ use it in your pipeline."#
|
||||
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
match stack.stdout() {
|
||||
OutDest::Pipe | OutDest::Capture => {
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => {
|
||||
child.stdout = Some(stdout);
|
||||
Ok(())
|
||||
}
|
||||
OutDest::Null => copy_pipe(stdout, io::sink(), &info),
|
||||
OutDest::Inherit => copy_pipe(stdout, io::stdout(), &info),
|
||||
OutDest::Print | OutDest::Inherit => {
|
||||
copy_pipe(stdout, io::stdout(), &info)
|
||||
}
|
||||
OutDest::File(file) => copy_pipe(stdout, file.as_ref(), &info),
|
||||
}?;
|
||||
}
|
||||
@ -191,14 +193,14 @@ use it in your pipeline."#
|
||||
let stderr_thread = if let Some(stderr) = child.stderr.take() {
|
||||
let info = info.clone();
|
||||
match stack.stderr() {
|
||||
OutDest::Pipe | OutDest::Capture => {
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => {
|
||||
child.stderr = Some(stderr);
|
||||
Ok(None)
|
||||
}
|
||||
OutDest::Null => {
|
||||
copy_pipe_on_thread(stderr, io::sink(), &info).map(Some)
|
||||
}
|
||||
OutDest::Inherit => {
|
||||
OutDest::Print | OutDest::Inherit => {
|
||||
copy_pipe_on_thread(stderr, io::stderr(), &info).map(Some)
|
||||
}
|
||||
OutDest::File(file) => {
|
||||
@ -213,12 +215,12 @@ use it in your pipeline."#
|
||||
let tee_thread = spawn_tee(info.clone(), eval_block)?;
|
||||
let tee = IoTee::new(stdout, tee_thread);
|
||||
match stack.stdout() {
|
||||
OutDest::Pipe | OutDest::Capture => {
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => {
|
||||
child.stdout = Some(ChildPipe::Tee(Box::new(tee)));
|
||||
Ok(())
|
||||
}
|
||||
OutDest::Null => copy(tee, io::sink(), &info),
|
||||
OutDest::Inherit => copy(tee, io::stdout(), &info),
|
||||
OutDest::Print | OutDest::Inherit => copy(tee, io::stdout(), &info),
|
||||
OutDest::File(file) => copy(tee, file.as_ref(), &info),
|
||||
}?;
|
||||
}
|
||||
@ -280,7 +282,7 @@ use it in your pipeline."#
|
||||
}
|
||||
|
||||
fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
|
||||
(Some(OutDest::Capture), Some(OutDest::Capture))
|
||||
(Some(OutDest::PipeSeparate), Some(OutDest::PipeSeparate))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ impl Command for Transpose {
|
||||
])
|
||||
.switch(
|
||||
"header-row",
|
||||
"treat the first row as column names",
|
||||
"use the first input column as the table header-row (or keynames when combined with --as-record)",
|
||||
Some('r'),
|
||||
)
|
||||
.switch(
|
||||
|
@ -57,31 +57,34 @@ impl Command for Wrap {
|
||||
vec![
|
||||
Example {
|
||||
description: "Wrap a list into a table with a given column name",
|
||||
example: "[1 2 3] | wrap num",
|
||||
example: "[ Pachisi Mahjong Catan Carcassonne ] | wrap game",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(1),
|
||||
"game" => Value::test_string("Pachisi"),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(2),
|
||||
"game" => Value::test_string("Mahjong"),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(3),
|
||||
"game" => Value::test_string("Catan"),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"game" => Value::test_string("Carcassonne"),
|
||||
}),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Wrap a range into a table with a given column name",
|
||||
example: "1..3 | wrap num",
|
||||
example: "4..6 | wrap num",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(1),
|
||||
"num" => Value::test_int(4),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(2),
|
||||
"num" => Value::test_int(5),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"num" => Value::test_int(3),
|
||||
"num" => Value::test_int(6),
|
||||
}),
|
||||
])),
|
||||
},
|
||||
|
@ -118,7 +118,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||
.map(|(x, y)| format!("{}: {}", x, local_into_string(y, ", ", config)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator),
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id),
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id.get()),
|
||||
Value::Nothing { .. } => String::new(),
|
||||
Value::Error { error, .. } => format!("{error:?}"),
|
||||
Value::Binary { val, .. } => format!("{val:?}"),
|
||||
|
@ -49,6 +49,7 @@ where
|
||||
.category(Category::Hash)
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::Any),
|
||||
(Type::Binary, Type::Any),
|
||||
(Type::table(), Type::table()),
|
||||
(Type::record(), Type::record()),
|
||||
])
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use nu_protocol::{engine::CommandType, BlockId};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
@ -44,7 +44,8 @@ impl Command for Source {
|
||||
// Note: this hidden positional is the block_id that corresponded to the 0th position
|
||||
// it is put here by the parser
|
||||
let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
|
||||
let block = engine_state.get_block(block_id as usize).clone();
|
||||
let block_id = BlockId::new(block_id as usize);
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
|
||||
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
|
||||
|
||||
|
@ -314,7 +314,7 @@ fn send_form_request(
|
||||
Value::List { ref vals, .. } => {
|
||||
if vals.len() % 2 != 0 {
|
||||
return Err(ShellErrorOrRequestError::ShellError(ShellError::IncorrectValue {
|
||||
msg: "Body type 'list' for form requests requires paired values. E.g.: [foo, 10]".into(),
|
||||
msg: "Body type 'list' for form requests requires paired values. E.g.: [foo, 10]".into(),
|
||||
val_span: body.span(),
|
||||
call_span: span,
|
||||
}));
|
||||
@ -901,6 +901,7 @@ fn retrieve_http_proxy_from_env(engine_state: &EngineState, stack: &mut Stack) -
|
||||
.or(stack.get_env_var(engine_state, "https_proxy"))
|
||||
.or(stack.get_env_var(engine_state, "HTTPS_PROXY"))
|
||||
.or(stack.get_env_var(engine_state, "ALL_PROXY"))
|
||||
.cloned()
|
||||
.and_then(|proxy| proxy.coerce_into_string().ok())
|
||||
}
|
||||
|
||||
|
@ -300,14 +300,14 @@ impl UrlComponents {
|
||||
return Ok(true);
|
||||
}
|
||||
match key {
|
||||
"host" => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "non-empty string".into(),
|
||||
value: "empty string".into(),
|
||||
"host" => Err(ShellError::InvalidValue {
|
||||
valid: "a non-empty string".into(),
|
||||
actual: format!("'{s}'"),
|
||||
span: value_span,
|
||||
}),
|
||||
"scheme" => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "non-empty string".into(),
|
||||
value: "empty string".into(),
|
||||
"scheme" => Err(ShellError::InvalidValue {
|
||||
valid: "a non-empty string".into(),
|
||||
actual: format!("'{s}'"),
|
||||
span: value_span,
|
||||
}),
|
||||
_ => Ok(false),
|
||||
|
@ -2,7 +2,7 @@ use crossterm::event::{
|
||||
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
|
||||
EnableMouseCapture, KeyCode, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use crossterm::terminal;
|
||||
use crossterm::{execute, terminal};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use num_traits::AsPrimitive;
|
||||
@ -81,8 +81,36 @@ There are 4 `key_type` variants:
|
||||
let head = call.head;
|
||||
let event_type_filter = get_event_type_filter(engine_state, stack, call, head)?;
|
||||
let add_raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let config = engine_state.get_config();
|
||||
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
if config.use_kitty_protocol {
|
||||
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
||||
println!("WARN: The terminal doesn't support use_kitty_protocol config.\r");
|
||||
}
|
||||
|
||||
// enable kitty protocol
|
||||
//
|
||||
// Note that, currently, only the following support this protocol:
|
||||
// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
|
||||
// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
|
||||
// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
|
||||
// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
|
||||
// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
|
||||
// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
|
||||
// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
|
||||
// * [ghostty terminal](https://github.com/ghostty-org/ghostty/pull/317)
|
||||
//
|
||||
// Refer to https://sw.kovidgoyal.net/kitty/keyboard-protocol/ if you're curious.
|
||||
let _ = execute!(
|
||||
stdout(),
|
||||
crossterm::event::PushKeyboardEnhancementFlags(
|
||||
crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let console_state = event_type_filter.enable_events()?;
|
||||
loop {
|
||||
let event = crossterm::event::read().map_err(|_| ShellError::GenericError {
|
||||
@ -95,6 +123,13 @@ There are 4 `key_type` variants:
|
||||
let event = parse_event(head, &event, &event_type_filter, add_raw);
|
||||
if let Some(event) = event {
|
||||
terminal::disable_raw_mode()?;
|
||||
if config.use_kitty_protocol {
|
||||
let _ = execute!(
|
||||
std::io::stdout(),
|
||||
crossterm::event::PopKeyboardEnhancementFlags
|
||||
);
|
||||
}
|
||||
|
||||
console_state.restore();
|
||||
return Ok(event.into_pipeline_data());
|
||||
}
|
||||
|
@ -14,7 +14,11 @@ impl Command for SubCommand {
|
||||
Signature::build("random binary")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Binary)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("length", SyntaxShape::Int, "Length of the output binary.")
|
||||
.required(
|
||||
"length",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Int, SyntaxShape::Filesize]),
|
||||
"Length of the output binary.",
|
||||
)
|
||||
.category(Category::Random)
|
||||
}
|
||||
|
||||
@ -43,11 +47,18 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Generate 16 random bytes",
|
||||
example: "random binary 16",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Generate 16 random bytes",
|
||||
example: "random binary 16",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate 1 random kilobyte",
|
||||
example: "random binary 1kb",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.allow_variants_without_examples(true)
|
||||
.named(
|
||||
"length",
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Int, SyntaxShape::Filesize]),
|
||||
"Number of chars (default 25)",
|
||||
Some('l'),
|
||||
)
|
||||
@ -58,6 +58,11 @@ impl Command for SubCommand {
|
||||
example: "random chars --length 20",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate one kilobyte of random chars",
|
||||
example: "random chars --length 1kb",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,351 +1,286 @@
|
||||
use alphanumeric_sort::compare_str;
|
||||
use nu_engine::column::nonexistent_column;
|
||||
use nu_protocol::{ShellError, Span, Value};
|
||||
use nu_engine::ClosureEval;
|
||||
use nu_protocol::{ast::CellPath, PipelineData, Record, ShellError, Span, Value};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// This module includes sorting functionality that is useful in sort-by and elsewhere.
|
||||
// Eventually it would be nice to find a better home for it; sorting logic is only coupled
|
||||
// to commands for historical reasons.
|
||||
/// A specification of sort order for `sort_by`.
|
||||
///
|
||||
/// A closure comparator allows the user to return custom ordering to sort by.
|
||||
/// A cell path comparator uses the value referred to by the cell path as the sorting key.
|
||||
pub enum Comparator {
|
||||
KeyClosure(ClosureEval),
|
||||
CustomClosure(ClosureEval),
|
||||
CellPath(CellPath),
|
||||
}
|
||||
|
||||
/// Sort a value. This only makes sense for lists and list-like things,
|
||||
/// so for everything else we just return the value as-is.
|
||||
/// CustomValues are converted to their base value and then sorted.
|
||||
pub fn sort_value(
|
||||
val: &Value,
|
||||
sort_columns: Vec<String>,
|
||||
ascending: bool,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
let span = val.span();
|
||||
match val {
|
||||
Value::List { vals, .. } => {
|
||||
let mut vals = vals.clone();
|
||||
sort(&mut vals, sort_columns, span, insensitive, natural)?;
|
||||
/// Sort a slice of `Value`s.
|
||||
///
|
||||
/// Sort has the following invariants, in order of precedence:
|
||||
/// - Null values (Nothing type) are always sorted to the end.
|
||||
/// - For natural sort, numeric values (numeric strings, ints, and floats) appear first, sorted by numeric value
|
||||
/// - Values appear by order of `Value`'s `PartialOrd`.
|
||||
/// - Sorting for values with equal ordering is stable.
|
||||
///
|
||||
/// Generally, values of different types are ordered by order of appearance in the `Value` enum.
|
||||
/// However, this is not always the case. For example, ints and floats will be grouped together since
|
||||
/// `Value`'s `PartialOrd` defines a non-decreasing ordering between non-decreasing integers and floats.
|
||||
pub fn sort(vec: &mut [Value], insensitive: bool, natural: bool) -> Result<(), ShellError> {
|
||||
// allow the comparator function to indicate error
|
||||
// by mutating this option captured by the closure,
|
||||
// since sort_by closure must be infallible
|
||||
let mut compare_err: Option<ShellError> = None;
|
||||
|
||||
if !ascending {
|
||||
vals.reverse();
|
||||
}
|
||||
|
||||
Ok(Value::list(vals, span))
|
||||
vec.sort_by(|a, b| {
|
||||
// we've already hit an error, bail out now
|
||||
if compare_err.is_some() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
Value::Custom { val, .. } => {
|
||||
let base_val = val.to_base_value(span)?;
|
||||
sort_value(&base_val, sort_columns, ascending, insensitive, natural)
|
||||
}
|
||||
_ => Ok(val.to_owned()),
|
||||
|
||||
compare_values(a, b, insensitive, natural).unwrap_or_else(|err| {
|
||||
compare_err.get_or_insert(err);
|
||||
Ordering::Equal
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(err) = compare_err {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort a value in-place. This is more efficient than sort_value() because it
|
||||
/// avoids cloning, but it does not work for CustomValues; they are returned as-is.
|
||||
pub fn sort_value_in_place(
|
||||
val: &mut Value,
|
||||
sort_columns: Vec<String>,
|
||||
ascending: bool,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let span = val.span();
|
||||
if let Value::List { vals, .. } = val {
|
||||
sort(vals, sort_columns, span, insensitive, natural)?;
|
||||
if !ascending {
|
||||
vals.reverse();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sort(
|
||||
/// Sort a slice of `Value`s by criteria specified by one or multiple `Comparator`s.
|
||||
pub fn sort_by(
|
||||
vec: &mut [Value],
|
||||
sort_columns: Vec<String>,
|
||||
span: Span,
|
||||
mut comparators: Vec<Comparator>,
|
||||
head_span: Span,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let val_span = vec.first().map(|v| v.span()).unwrap_or(span);
|
||||
match vec.first() {
|
||||
Some(Value::Record { val: record, .. }) => {
|
||||
if sort_columns.is_empty() {
|
||||
// This uses the same format as the 'requires a column name' error in split_by.rs
|
||||
return Err(ShellError::GenericError {
|
||||
error: "expected name".into(),
|
||||
msg: "requires a column name to sort table data".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: nonexistent,
|
||||
span: Some(span),
|
||||
src_span: val_span,
|
||||
});
|
||||
}
|
||||
|
||||
// check to make sure each value in each column in the record
|
||||
// that we asked for is a string. So, first collect all the columns
|
||||
// that we asked for into vals, then later make sure they're all
|
||||
// strings.
|
||||
let mut vals = vec![];
|
||||
for item in vec.iter() {
|
||||
for col in &sort_columns {
|
||||
let val = item
|
||||
.get_data_by_key(col)
|
||||
.unwrap_or_else(|| Value::nothing(Span::unknown()));
|
||||
vals.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
let should_sort_case_insensitively = insensitive
|
||||
&& vals
|
||||
.iter()
|
||||
.all(|x| matches!(x.get_type(), nu_protocol::Type::String));
|
||||
|
||||
let should_sort_case_naturally = natural
|
||||
&& vals
|
||||
.iter()
|
||||
.all(|x| matches!(x.get_type(), nu_protocol::Type::String));
|
||||
|
||||
vec.sort_by(|a, b| {
|
||||
compare(
|
||||
a,
|
||||
b,
|
||||
&sort_columns,
|
||||
span,
|
||||
should_sort_case_insensitively,
|
||||
should_sort_case_naturally,
|
||||
)
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
vec.sort_by(|a, b| {
|
||||
if insensitive {
|
||||
let span_a = a.span();
|
||||
let span_b = b.span();
|
||||
let folded_left = match a {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_a),
|
||||
_ => a.clone(),
|
||||
};
|
||||
|
||||
let folded_right = match b {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_b),
|
||||
_ => b.clone(),
|
||||
};
|
||||
|
||||
if natural {
|
||||
match (
|
||||
folded_left.coerce_into_string(),
|
||||
folded_right.coerce_into_string(),
|
||||
) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
folded_left
|
||||
.partial_cmp(&folded_right)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
}
|
||||
} else if natural {
|
||||
match (a.coerce_str(), b.coerce_str()) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
a.partial_cmp(b).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
});
|
||||
}
|
||||
if comparators.is_empty() {
|
||||
// This uses the same format as the 'requires a column name' error in split_by.rs
|
||||
return Err(ShellError::GenericError {
|
||||
error: "expected name".into(),
|
||||
msg: "requires a cell path or closure to sort data".into(),
|
||||
span: Some(head_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
// allow the comparator function to indicate error
|
||||
// by mutating this option captured by the closure,
|
||||
// since sort_by closure must be infallible
|
||||
let mut compare_err: Option<ShellError> = None;
|
||||
|
||||
vec.sort_by(|a, b| {
|
||||
compare_by(
|
||||
a,
|
||||
b,
|
||||
&mut comparators,
|
||||
head_span,
|
||||
insensitive,
|
||||
natural,
|
||||
&mut compare_err,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(err) = compare_err {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compare(
|
||||
/// Sort a record's key-value pairs.
|
||||
///
|
||||
/// Can sort by key or by value.
|
||||
pub fn sort_record(
|
||||
record: Record,
|
||||
sort_by_value: bool,
|
||||
reverse: bool,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Record, ShellError> {
|
||||
let mut input_pairs: Vec<(String, Value)> = record.into_iter().collect();
|
||||
|
||||
// allow the comparator function to indicate error
|
||||
// by mutating this option captured by the closure,
|
||||
// since sort_by closure must be infallible
|
||||
let mut compare_err: Option<ShellError> = None;
|
||||
|
||||
if sort_by_value {
|
||||
input_pairs.sort_by(|a, b| {
|
||||
// we've already hit an error, bail out now
|
||||
if compare_err.is_some() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
compare_values(&a.1, &b.1, insensitive, natural).unwrap_or_else(|err| {
|
||||
compare_err.get_or_insert(err);
|
||||
Ordering::Equal
|
||||
})
|
||||
});
|
||||
} else {
|
||||
input_pairs.sort_by(|a, b| compare_strings(&a.0, &b.0, insensitive, natural));
|
||||
};
|
||||
|
||||
if let Some(err) = compare_err {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if reverse {
|
||||
input_pairs.reverse()
|
||||
}
|
||||
|
||||
Ok(input_pairs.into_iter().collect())
|
||||
}
|
||||
|
||||
pub fn compare_by(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
columns: &[String],
|
||||
comparators: &mut [Comparator],
|
||||
span: Span,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
error: &mut Option<ShellError>,
|
||||
) -> Ordering {
|
||||
for column in columns {
|
||||
let left_value = left.get_data_by_key(column);
|
||||
|
||||
let left_res = match left_value {
|
||||
Some(left_res) => left_res,
|
||||
None => Value::nothing(span),
|
||||
};
|
||||
|
||||
let right_value = right.get_data_by_key(column);
|
||||
|
||||
let right_res = match right_value {
|
||||
Some(right_res) => right_res,
|
||||
None => Value::nothing(span),
|
||||
};
|
||||
|
||||
let result = if insensitive {
|
||||
let span_left = left_res.span();
|
||||
let span_right = right_res.span();
|
||||
let folded_left = match left_res {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_left),
|
||||
_ => left_res,
|
||||
};
|
||||
|
||||
let folded_right = match right_res {
|
||||
Value::String { val, .. } => Value::string(val.to_folded_case(), span_right),
|
||||
_ => right_res,
|
||||
};
|
||||
if natural {
|
||||
match (
|
||||
folded_left.coerce_into_string(),
|
||||
folded_right.coerce_into_string(),
|
||||
) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
folded_left
|
||||
.partial_cmp(&folded_right)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
// we've already hit an error, bail out now
|
||||
if error.is_some() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
for cmp in comparators.iter_mut() {
|
||||
let result = match cmp {
|
||||
Comparator::CellPath(cell_path) => {
|
||||
compare_cell_path(left, right, cell_path, insensitive, natural)
|
||||
}
|
||||
} else if natural {
|
||||
match (
|
||||
left_res.coerce_into_string(),
|
||||
right_res.coerce_into_string(),
|
||||
) {
|
||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||
_ => Ordering::Equal,
|
||||
Comparator::KeyClosure(closure) => {
|
||||
compare_key_closure(left, right, closure, span, insensitive, natural)
|
||||
}
|
||||
Comparator::CustomClosure(closure) => {
|
||||
compare_custom_closure(left, right, closure, span)
|
||||
}
|
||||
} else {
|
||||
left_res.partial_cmp(&right_res).unwrap_or(Ordering::Equal)
|
||||
};
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
match result {
|
||||
Ok(Ordering::Equal) => {}
|
||||
Ok(ordering) => return ordering,
|
||||
Err(err) => {
|
||||
// don't bother continuing through the remaining comparators as we've hit an error
|
||||
// don't overwrite if there's an existing error
|
||||
error.get_or_insert(err);
|
||||
return Ordering::Equal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_protocol::{record, Value};
|
||||
/// Determines whether a value should be sorted as a string
|
||||
///
|
||||
/// If we're natural sorting, we want to sort strings, integers, and floats alphanumerically, so we should string sort.
|
||||
/// Otherwise, we only want to string sort if both values are strings or globs (to enable case insensitive comparison)
|
||||
fn should_sort_as_string(val: &Value, natural: bool) -> bool {
|
||||
matches!(
|
||||
(val, natural),
|
||||
(&Value::String { .. }, _)
|
||||
| (&Value::Glob { .. }, _)
|
||||
| (&Value::Int { .. }, true)
|
||||
| (&Value::Float { .. }, true)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_value() {
|
||||
let val = Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
]);
|
||||
/// Simple wrapper around `should_sort_as_string` to determine if two values
|
||||
/// should be compared as strings.
|
||||
fn should_string_compare(left: &Value, right: &Value, natural: bool) -> bool {
|
||||
should_sort_as_string(left, natural) && should_sort_as_string(right, natural)
|
||||
}
|
||||
|
||||
let sorted_alphabetically =
|
||||
sort_value(&val, vec!["fruit".to_string()], true, false, false).unwrap();
|
||||
assert_eq!(
|
||||
sorted_alphabetically,
|
||||
Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
],)
|
||||
);
|
||||
|
||||
let sorted_by_count_desc =
|
||||
sort_value(&val, vec!["count".to_string()], false, false, false).unwrap();
|
||||
assert_eq!(
|
||||
sorted_by_count_desc,
|
||||
Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
],)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_value_in_place() {
|
||||
let mut val = Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
]);
|
||||
|
||||
sort_value_in_place(&mut val, vec!["fruit".to_string()], true, false, false).unwrap();
|
||||
assert_eq!(
|
||||
val,
|
||||
Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
],)
|
||||
);
|
||||
|
||||
sort_value_in_place(&mut val, vec!["count".to_string()], false, false, false).unwrap();
|
||||
assert_eq!(
|
||||
val,
|
||||
Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("apple"),
|
||||
"count" => Value::test_int(9),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("orange"),
|
||||
"count" => Value::test_int(7),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"fruit" => Value::test_string("pear"),
|
||||
"count" => Value::test_int(3),
|
||||
}),
|
||||
],)
|
||||
);
|
||||
pub fn compare_values(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
if should_string_compare(left, right, natural) {
|
||||
Ok(compare_strings(
|
||||
&left.coerce_str()?,
|
||||
&right.coerce_str()?,
|
||||
insensitive,
|
||||
natural,
|
||||
))
|
||||
} else {
|
||||
Ok(left.partial_cmp(right).unwrap_or(Ordering::Equal))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compare_strings(left: &str, right: &str, insensitive: bool, natural: bool) -> Ordering {
|
||||
fn compare_inner<T>(left: T, right: T, natural: bool) -> Ordering
|
||||
where
|
||||
T: AsRef<str> + Ord,
|
||||
{
|
||||
if natural {
|
||||
alphanumeric_sort::compare_str(left, right)
|
||||
} else {
|
||||
left.cmp(&right)
|
||||
}
|
||||
}
|
||||
|
||||
// only allocate a String if necessary for case folding
|
||||
if insensitive {
|
||||
compare_inner(left.to_folded_case(), right.to_folded_case(), natural)
|
||||
} else {
|
||||
compare_inner(left, right, natural)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compare_cell_path(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
cell_path: &CellPath,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
let left = left.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
let right = right.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
compare_values(&left, &right, insensitive, natural)
|
||||
}
|
||||
|
||||
pub fn compare_key_closure(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
closure_eval: &mut ClosureEval,
|
||||
span: Span,
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
let left_key = closure_eval
|
||||
.run_with_value(left.clone())?
|
||||
.into_value(span)?;
|
||||
let right_key = closure_eval
|
||||
.run_with_value(right.clone())?
|
||||
.into_value(span)?;
|
||||
compare_values(&left_key, &right_key, insensitive, natural)
|
||||
}
|
||||
|
||||
pub fn compare_custom_closure(
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
closure_eval: &mut ClosureEval,
|
||||
span: Span,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
closure_eval
|
||||
.add_arg(left.clone())
|
||||
.add_arg(right.clone())
|
||||
.run_with_input(PipelineData::Value(
|
||||
Value::list(vec![left.clone(), right.clone()], span),
|
||||
None,
|
||||
))
|
||||
.and_then(|data| data.into_value(span))
|
||||
.map(|val| {
|
||||
if val.is_true() {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -38,11 +38,11 @@ pub struct DecodeBase64;
|
||||
|
||||
impl Command for DecodeBase64 {
|
||||
fn name(&self) -> &str {
|
||||
"decode new-base64"
|
||||
"decode base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("decode new-base64")
|
||||
Signature::build("decode base64")
|
||||
.input_output_types(vec![(Type::String, Type::Binary)])
|
||||
.allow_variants_without_examples(true)
|
||||
.switch("url", "Decode the URL-safe Base64 version.", None)
|
||||
@ -62,17 +62,17 @@ impl Command for DecodeBase64 {
|
||||
vec![
|
||||
Example {
|
||||
description: "Decode a Base64 string",
|
||||
example: r#""U29tZSBEYXRh" | decode new-base64 | decode"#,
|
||||
example: r#""U29tZSBEYXRh" | decode base64 | decode"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Decode arbitrary data",
|
||||
example: r#""/w==" | decode new-base64"#,
|
||||
example: r#""/w==" | decode base64"#,
|
||||
result: Some(Value::test_binary(vec![0xFF])),
|
||||
},
|
||||
Example {
|
||||
description: "Decode a URL-safe Base64 string",
|
||||
example: r#""_w==" | decode new-base64 --url"#,
|
||||
example: r#""_w==" | decode base64 --url"#,
|
||||
result: Some(Value::test_binary(vec![0xFF])),
|
||||
},
|
||||
]
|
||||
@ -109,11 +109,11 @@ pub struct EncodeBase64;
|
||||
|
||||
impl Command for EncodeBase64 {
|
||||
fn name(&self) -> &str {
|
||||
"encode new-base64"
|
||||
"encode base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("encode new-base64")
|
||||
Signature::build("encode base64")
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::String),
|
||||
(Type::Binary, Type::String),
|
||||
@ -135,17 +135,17 @@ impl Command for EncodeBase64 {
|
||||
vec![
|
||||
Example {
|
||||
description: "Encode a string with Base64",
|
||||
example: r#""Alphabet from A to Z" | encode new-base64"#,
|
||||
example: r#""Alphabet from A to Z" | encode base64"#,
|
||||
result: Some(Value::test_string("QWxwaGFiZXQgZnJvbSBBIHRvIFo=")),
|
||||
},
|
||||
Example {
|
||||
description: "Encode arbitrary data",
|
||||
example: r#"0x[BE EE FF] | encode new-base64"#,
|
||||
example: r#"0x[BE EE FF] | encode base64"#,
|
||||
result: Some(Value::test_string("vu7/")),
|
||||
},
|
||||
Example {
|
||||
description: "Use a URL-safe alphabet",
|
||||
example: r#"0x[BE EE FF] | encode new-base64 --url"#,
|
||||
example: r#"0x[BE EE FF] | encode base64 --url"#,
|
||||
result: Some(Value::test_string("vu7_")),
|
||||
},
|
||||
]
|
||||
|
@ -1,377 +0,0 @@
|
||||
use base64::{
|
||||
alphabet,
|
||||
engine::{
|
||||
general_purpose::{NO_PAD, PAD},
|
||||
GeneralPurpose,
|
||||
},
|
||||
Engine,
|
||||
};
|
||||
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
||||
use nu_protocol::{
|
||||
ast::CellPath,
|
||||
engine::{Call, EngineState},
|
||||
PipelineData, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
|
||||
pub const CHARACTER_SET_DESC: &str = "specify the character rules for encoding the input.\n\
|
||||
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
||||
'binhex', 'bcrypt', 'crypt', 'mutf7'";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Base64Config {
|
||||
pub character_set: Spanned<String>,
|
||||
pub action_type: ActionType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ActionType {
|
||||
Encode,
|
||||
Decode,
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
binary: bool,
|
||||
encoding_config: Base64Config,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.cell_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Base64CommandArguments {
|
||||
pub(super) character_set: Option<Spanned<String>>,
|
||||
pub(super) action_type: ActionType,
|
||||
pub(super) binary: bool,
|
||||
}
|
||||
|
||||
pub fn operate(
|
||||
engine_state: &EngineState,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
cell_paths: Vec<CellPath>,
|
||||
args: Base64CommandArguments,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
|
||||
// Default the character set to standard if the argument is not specified.
|
||||
let character_set = match args.character_set {
|
||||
Some(inner_tag) => inner_tag,
|
||||
None => Spanned {
|
||||
item: "standard".to_string(),
|
||||
span: head, // actually this span is always useless, because default character_set is always valid.
|
||||
},
|
||||
};
|
||||
|
||||
let args = Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set,
|
||||
action_type: args.action_type,
|
||||
},
|
||||
binary: args.binary,
|
||||
cell_paths,
|
||||
};
|
||||
|
||||
general_operate(action, args, input, call.head, engine_state.signals())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
// only used for `decode` action
|
||||
args: &Arguments,
|
||||
command_span: Span,
|
||||
) -> Value {
|
||||
let base64_config = &args.encoding_config;
|
||||
let output_binary = args.binary;
|
||||
|
||||
let config_character_set = &base64_config.character_set;
|
||||
let base64_engine: GeneralPurpose = match config_character_set.item.as_str() {
|
||||
"standard" => GeneralPurpose::new(&alphabet::STANDARD, PAD),
|
||||
"standard-no-padding" => GeneralPurpose::new(&alphabet::STANDARD, NO_PAD),
|
||||
"url-safe" => GeneralPurpose::new(&alphabet::URL_SAFE, PAD),
|
||||
"url-safe-no-padding" => GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD),
|
||||
"bcrypt" => GeneralPurpose::new(&alphabet::BCRYPT, NO_PAD),
|
||||
"binhex" => GeneralPurpose::new(&alphabet::BIN_HEX, NO_PAD),
|
||||
"crypt" => GeneralPurpose::new(&alphabet::CRYPT, NO_PAD),
|
||||
"mutf7" => GeneralPurpose::new(&alphabet::IMAP_MUTF7, NO_PAD),
|
||||
not_valid => return Value::error (
|
||||
ShellError::GenericError {
|
||||
error: "value is not an accepted character set".into(),
|
||||
msg: format!(
|
||||
"{not_valid} is not a valid character-set.\nPlease use `help encode base64` to see a list of valid character sets."
|
||||
),
|
||||
span: Some(config_character_set.span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
}, config_character_set.span)
|
||||
};
|
||||
let value_span = input.span();
|
||||
match input {
|
||||
// Propagate existing errors.
|
||||
Value::Error { .. } => input.clone(),
|
||||
Value::Binary { val, .. } => match base64_config.action_type {
|
||||
ActionType::Encode => {
|
||||
let mut enc_vec = vec![0; val.len() * 4 / 3 + 4];
|
||||
let bytes_written = match base64_engine.encode_slice(val, &mut enc_vec) {
|
||||
Ok(bytes_written) => bytes_written,
|
||||
Err(e) => {
|
||||
return Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Error encoding data".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(value_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
value_span,
|
||||
)
|
||||
}
|
||||
};
|
||||
enc_vec.truncate(bytes_written);
|
||||
Value::string(std::str::from_utf8(&enc_vec).unwrap_or(""), command_span)
|
||||
}
|
||||
ActionType::Decode => Value::error(
|
||||
ShellError::UnsupportedInput {
|
||||
msg: "Binary data can only be encoded".to_string(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: command_span,
|
||||
input_span: input.span(),
|
||||
},
|
||||
command_span,
|
||||
),
|
||||
},
|
||||
Value::String { val, .. } => {
|
||||
match base64_config.action_type {
|
||||
ActionType::Encode => {
|
||||
let mut enc_str = String::new();
|
||||
base64_engine.encode_string(val, &mut enc_str);
|
||||
Value::string(enc_str, command_span)
|
||||
}
|
||||
|
||||
ActionType::Decode => {
|
||||
// for decode, input val may contains invalid new line character, which is ok to omitted them by default.
|
||||
let val = val.clone();
|
||||
let val = val.replace("\r\n", "").replace('\n', "");
|
||||
|
||||
match base64_engine.decode(val) {
|
||||
Ok(decoded_value) => {
|
||||
if output_binary {
|
||||
Value::binary(decoded_value, command_span)
|
||||
} else {
|
||||
match String::from_utf8(decoded_value) {
|
||||
Ok(string_value) => Value::string(string_value, command_span),
|
||||
Err(e) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "base64 payload isn't a valid utf-8 sequence"
|
||||
.into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(value_span),
|
||||
help: Some(
|
||||
"consider using the `--binary` flag".to_owned(),
|
||||
),
|
||||
inner: vec![],
|
||||
},
|
||||
value_span,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "value could not be base64 decoded".into(),
|
||||
msg: format!(
|
||||
"invalid base64 input for character set {}",
|
||||
&config_character_set.item
|
||||
),
|
||||
span: Some(command_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
command_span,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
other => Value::error(
|
||||
ShellError::TypeMismatch {
|
||||
err_message: format!("string or binary, not {}", other.get_type()),
|
||||
span: other.span(),
|
||||
},
|
||||
other.span(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, ActionType, Arguments, Base64Config};
|
||||
use nu_protocol::{Span, Spanned, Value};
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard() {
|
||||
let word = Value::test_string("Some Data Padding");
|
||||
let expected = Value::test_string("U29tZSBEYXRhIFBhZGRpbmc=");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "standard".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard_no_padding() {
|
||||
let word = Value::test_string("Some Data Padding");
|
||||
let expected = Value::test_string("U29tZSBEYXRhIFBhZGRpbmc");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "standard-no-padding".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_url_safe() {
|
||||
let word = Value::test_string("this is for url");
|
||||
let expected = Value::test_string("dGhpcyBpcyBmb3IgdXJs");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "url-safe".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_binhex() {
|
||||
let word = Value::test_string(r#"B5"LD@jSCAJJG'9cG!"#);
|
||||
let expected = Value::binary(b"a binhex test".as_slice(), Span::test_data());
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "binhex".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_binhex_with_new_line_input() {
|
||||
let word = Value::test_string("B5\"LD@jSC\nAJJG'9cG!");
|
||||
let expected = Value::binary(b"a binhex test".as_slice(), Span::test_data());
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "binhex".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_binary() {
|
||||
let word = Value::binary(vec![77, 97, 110], Span::test_data());
|
||||
let expected = Value::test_string("TWFu");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "standard".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_binary_expect_error() {
|
||||
let word = Value::binary(vec![77, 97, 110], Span::test_data());
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: Base64Config {
|
||||
character_set: Spanned {
|
||||
item: "standard".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
binary: true,
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
match actual {
|
||||
Value::Error { .. } => {}
|
||||
_ => panic!("the result should be Value::Error"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::report_shell_warning;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DecodeBase64Old;
|
||||
|
||||
impl Command for DecodeBase64Old {
|
||||
fn name(&self) -> &str {
|
||||
"decode base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("decode base64")
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::Any),
|
||||
(
|
||||
Type::List(Box::new(Type::String)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
),
|
||||
(Type::table(), Type::table()),
|
||||
(Type::record(), Type::record()),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.named(
|
||||
"character-set",
|
||||
SyntaxShape::String,
|
||||
CHARACTER_SET_DESC,
|
||||
Some('c'),
|
||||
)
|
||||
.switch(
|
||||
"binary",
|
||||
"Output a binary value instead of decoding payload as UTF-8",
|
||||
Some('b'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, decode data at the given cell paths.",
|
||||
)
|
||||
.category(Category::Hash)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Base64 decode a value."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"Will attempt to decode binary payload as an UTF-8 string by default. Use the `--binary(-b)` argument to force binary output."#
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Base64 decode a value and output as UTF-8 string",
|
||||
example: "'U29tZSBEYXRh' | decode base64",
|
||||
result: Some(Value::test_string("Some Data")),
|
||||
},
|
||||
Example {
|
||||
description: "Base64 decode a value and output as binary",
|
||||
example: "'U29tZSBEYXRh' | decode base64 --binary",
|
||||
result: Some(Value::binary(
|
||||
[0x53, 0x6f, 0x6d, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
report_shell_warning(
|
||||
engine_state,
|
||||
&ShellError::Deprecated {
|
||||
old_command: "decode base64".into(),
|
||||
new_suggestion: "the new `decode new-base64` version".into(),
|
||||
span: call.head,
|
||||
url: "`help decode new-base64`".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let character_set: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "character-set")?;
|
||||
let binary = call.has_flag(engine_state, stack, "binary")?;
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let args = Base64CommandArguments {
|
||||
action_type: ActionType::Decode,
|
||||
binary,
|
||||
character_set,
|
||||
};
|
||||
operate(engine_state, call, input, cell_paths, args)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let character_set: Option<Spanned<String>> =
|
||||
call.get_flag_const(working_set, "character-set")?;
|
||||
let binary = call.has_flag_const(working_set, "binary")?;
|
||||
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
|
||||
let args = Base64CommandArguments {
|
||||
action_type: ActionType::Decode,
|
||||
binary,
|
||||
character_set,
|
||||
};
|
||||
operate(working_set.permanent(), call, input, cell_paths, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
crate::test_examples(DecodeBase64Old)
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::report_shell_warning;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EncodeBase64Old;
|
||||
|
||||
impl Command for EncodeBase64Old {
|
||||
fn name(&self) -> &str {
|
||||
"encode base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("encode base64")
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::String),
|
||||
(Type::Binary, Type::String),
|
||||
(
|
||||
Type::List(Box::new(Type::String)),
|
||||
Type::List(Box::new(Type::String)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::String)),
|
||||
),
|
||||
// Relaxed for heterogeneous list.
|
||||
// Should be removed as soon as the type system supports better restrictions
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::String)),
|
||||
),
|
||||
(Type::table(), Type::table()),
|
||||
(Type::record(), Type::record()),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.named(
|
||||
"character-set",
|
||||
SyntaxShape::String,
|
||||
CHARACTER_SET_DESC,
|
||||
Some('c'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, encode data at the given cell paths.",
|
||||
)
|
||||
.category(Category::Hash)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Encode a string or binary value using Base64."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Encode binary data",
|
||||
example: "0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode base64",
|
||||
result: Some(Value::test_string("CfkRAp1041vYQVbFY1aIwA==")),
|
||||
},
|
||||
Example {
|
||||
description: "Encode a string with default settings",
|
||||
example: "'Some Data' | encode base64",
|
||||
result: Some(Value::test_string("U29tZSBEYXRh")),
|
||||
},
|
||||
Example {
|
||||
description: "Encode a string with the binhex character set",
|
||||
example: "'Some Data' | encode base64 --character-set binhex",
|
||||
result: Some(Value::test_string(r#"8fpYC5"%BA4K"#)),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
report_shell_warning(
|
||||
engine_state,
|
||||
&ShellError::Deprecated {
|
||||
old_command: "encode base64".into(),
|
||||
new_suggestion: "the new `encode new-base64` version".into(),
|
||||
span: call.head,
|
||||
url: "`help encode new-base64`".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let character_set: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "character-set")?;
|
||||
let binary = call.has_flag(engine_state, stack, "binary")?;
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let args = Base64CommandArguments {
|
||||
action_type: ActionType::Encode,
|
||||
binary,
|
||||
character_set,
|
||||
};
|
||||
operate(engine_state, call, input, cell_paths, args)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let character_set: Option<Spanned<String>> =
|
||||
call.get_flag_const(working_set, "character-set")?;
|
||||
let binary = call.has_flag_const(working_set, "binary")?;
|
||||
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
|
||||
let args = Base64CommandArguments {
|
||||
action_type: ActionType::Encode,
|
||||
binary,
|
||||
character_set,
|
||||
};
|
||||
operate(working_set.permanent(), call, input, cell_paths, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
crate::test_examples(EncodeBase64Old)
|
||||
}
|
||||
}
|
@ -1,11 +1,6 @@
|
||||
mod base64;
|
||||
mod decode;
|
||||
mod decode_base64;
|
||||
mod encode;
|
||||
mod encode_base64;
|
||||
mod encoding;
|
||||
|
||||
pub use self::decode::Decode;
|
||||
pub use self::decode_base64::DecodeBase64Old;
|
||||
pub use self::encode::Encode;
|
||||
pub use self::encode_base64::EncodeBase64Old;
|
||||
|
@ -88,7 +88,25 @@ impl Command for FormatDate {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let list = call.has_flag(engine_state, stack, "list")?;
|
||||
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
|
||||
run(engine_state, call, input, list, format)
|
||||
|
||||
// get the locale first so we can use the proper get_env_var functions since this is a const command
|
||||
// we can override the locale by setting $env.NU_TEST_LOCALE_OVERRIDE or $env.LC_TIME
|
||||
let locale = if let Some(loc) = engine_state
|
||||
.get_env_var(LOCALE_OVERRIDE_ENV_VAR)
|
||||
.or_else(|| engine_state.get_env_var("LC_TIME"))
|
||||
{
|
||||
let locale_str = loc.as_str()?.split('.').next().unwrap_or("en_US");
|
||||
locale_str.try_into().unwrap_or(Locale::en_US)
|
||||
} else {
|
||||
get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_"))
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US)
|
||||
};
|
||||
|
||||
run(engine_state, call, input, list, format, locale)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
@ -99,7 +117,25 @@ impl Command for FormatDate {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let list = call.has_flag_const(working_set, "list")?;
|
||||
let format = call.opt_const::<Spanned<String>>(working_set, 0)?;
|
||||
run(working_set.permanent(), call, input, list, format)
|
||||
|
||||
// get the locale first so we can use the proper get_env_var functions since this is a const command
|
||||
// we can override the locale by setting $env.NU_TEST_LOCALE_OVERRIDE or $env.LC_TIME
|
||||
let locale = if let Some(loc) = working_set
|
||||
.get_env_var(LOCALE_OVERRIDE_ENV_VAR)
|
||||
.or_else(|| working_set.get_env_var("LC_TIME"))
|
||||
{
|
||||
let locale_str = loc.as_str()?.split('.').next().unwrap_or("en_US");
|
||||
locale_str.try_into().unwrap_or(Locale::en_US)
|
||||
} else {
|
||||
get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_"))
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US)
|
||||
};
|
||||
|
||||
run(working_set.permanent(), call, input, list, format, locale)
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,6 +145,7 @@ fn run(
|
||||
input: PipelineData,
|
||||
list: bool,
|
||||
format: Option<Spanned<String>>,
|
||||
locale: Locale,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
if list {
|
||||
@ -124,34 +161,23 @@ fn run(
|
||||
}
|
||||
input.map(
|
||||
move |value| match &format {
|
||||
Some(format) => format_helper(value, format.item.as_str(), format.span, head),
|
||||
Some(format) => format_helper(value, format.item.as_str(), format.span, head, locale),
|
||||
None => format_helper_rfc2822(value, head),
|
||||
},
|
||||
engine_state.signals(),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_from<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value
|
||||
fn format_from<Tz: TimeZone>(
|
||||
date_time: DateTime<Tz>,
|
||||
formatter: &str,
|
||||
span: Span,
|
||||
locale: Locale,
|
||||
) -> Value
|
||||
where
|
||||
Tz::Offset: Display,
|
||||
{
|
||||
let mut formatter_buf = String::new();
|
||||
// Format using locale LC_TIME
|
||||
let locale = if let Ok(l) =
|
||||
std::env::var(LOCALE_OVERRIDE_ENV_VAR).or_else(|_| std::env::var("LC_TIME"))
|
||||
{
|
||||
let locale_str = l.split('.').next().unwrap_or("en_US");
|
||||
locale_str.try_into().unwrap_or(Locale::en_US)
|
||||
} else {
|
||||
// LC_ALL > LC_CTYPE > LANG
|
||||
// Not locale present, default to en_US
|
||||
get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_")) // `chrono::Locale` needs something like `xx_xx`, rather than `xx-xx`
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US)
|
||||
};
|
||||
let format = date_time.format_localized(formatter, locale);
|
||||
|
||||
match formatter_buf.write_fmt(format_args!("{format}")) {
|
||||
@ -166,14 +192,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn format_helper(value: Value, formatter: &str, formatter_span: Span, head_span: Span) -> Value {
|
||||
fn format_helper(
|
||||
value: Value,
|
||||
formatter: &str,
|
||||
formatter_span: Span,
|
||||
head_span: Span,
|
||||
locale: Locale,
|
||||
) -> Value {
|
||||
match value {
|
||||
Value::Date { val, .. } => format_from(val, formatter, formatter_span),
|
||||
Value::Date { val, .. } => format_from(val, formatter, formatter_span, locale),
|
||||
Value::String { val, .. } => {
|
||||
let dt = parse_date_from_string(&val, formatter_span);
|
||||
|
||||
match dt {
|
||||
Ok(x) => format_from(x, formatter, formatter_span),
|
||||
Ok(x) => format_from(x, formatter, formatter_span, locale),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
@ -156,12 +156,17 @@ impl Command for SubCommand {
|
||||
result: Some(Value::test_string("my_library.nu")),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find string using regular expression",
|
||||
description: "Find and replace contents with capture group using regular expression, with escapes",
|
||||
example: "'hello=world' | str replace -r '\\$?(?<varname>.*)=(?<value>.*)' '$$$varname = $value'",
|
||||
result: Some(Value::test_string("$hello = world")),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of found string using regular expression",
|
||||
example: "'abc abc abc' | str replace --all --regex 'b' 'z'",
|
||||
result: Some(Value::test_string("azc azc azc")),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find string in table using regular expression",
|
||||
description: "Find and replace all occurrences of found string in table using regular expression",
|
||||
example:
|
||||
"[[ColA ColB ColC]; [abc abc ads]] | str replace --all --regex 'b' 'z' ColA ColC",
|
||||
result: Some(Value::test_list (
|
||||
@ -173,7 +178,7 @@ impl Command for SubCommand {
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find string in record using regular expression",
|
||||
description: "Find and replace all occurrences of found string in record using regular expression",
|
||||
example:
|
||||
"{ KeyA: abc, KeyB: abc, KeyC: ads } | str replace --all --regex 'b' 'z' KeyA KeyC",
|
||||
result: Some(Value::test_record(record! {
|
||||
|
@ -64,17 +64,19 @@ impl Command for SubCommand {
|
||||
"bytes" => Value::test_int(38),
|
||||
"chars" => Value::test_int(38),
|
||||
"graphemes" => Value::test_int(38),
|
||||
"unicode-width" => Value::test_int(38),
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
description: "Counts unicode characters",
|
||||
example: r#"'今天天气真好' | str stats "#,
|
||||
example: r#"'今天天气真好' | str stats"#,
|
||||
result: Some(Value::test_record(record! {
|
||||
"lines" => Value::test_int(1),
|
||||
"words" => Value::test_int(6),
|
||||
"bytes" => Value::test_int(18),
|
||||
"chars" => Value::test_int(6),
|
||||
"graphemes" => Value::test_int(6),
|
||||
"unicode-width" => Value::test_int(12),
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
@ -86,6 +88,7 @@ impl Command for SubCommand {
|
||||
"bytes" => Value::test_int(15),
|
||||
"chars" => Value::test_int(14),
|
||||
"graphemes" => Value::test_int(13),
|
||||
"unicode-width" => Value::test_int(13),
|
||||
})),
|
||||
},
|
||||
]
|
||||
@ -139,6 +142,7 @@ fn counter(contents: &str, span: Span) -> Value {
|
||||
"bytes" => get_count(&counts, Counter::Bytes, span),
|
||||
"chars" => get_count(&counts, Counter::CodePoints, span),
|
||||
"graphemes" => get_count(&counts, Counter::GraphemeClusters, span),
|
||||
"unicode-width" => get_count(&counts, Counter::UnicodeWidth, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
@ -208,6 +212,7 @@ impl Count for Counter {
|
||||
}
|
||||
Counter::Words => s.unicode_words().count(),
|
||||
Counter::CodePoints => s.chars().count(),
|
||||
Counter::UnicodeWidth => unicode_width::UnicodeWidthStr::width(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,15 +234,19 @@ pub enum Counter {
|
||||
|
||||
/// Counts unicode code points
|
||||
CodePoints,
|
||||
|
||||
/// Counts the width of the string
|
||||
UnicodeWidth,
|
||||
}
|
||||
|
||||
/// A convenience array of all counter types.
|
||||
pub const ALL_COUNTERS: [Counter; 5] = [
|
||||
pub const ALL_COUNTERS: [Counter; 6] = [
|
||||
Counter::GraphemeClusters,
|
||||
Counter::Bytes,
|
||||
Counter::Lines,
|
||||
Counter::Words,
|
||||
Counter::CodePoints,
|
||||
Counter::UnicodeWidth,
|
||||
];
|
||||
|
||||
impl fmt::Display for Counter {
|
||||
@ -248,6 +257,7 @@ impl fmt::Display for Counter {
|
||||
Counter::Lines => "lines",
|
||||
Counter::Words => "words",
|
||||
Counter::CodePoints => "codepoints",
|
||||
Counter::UnicodeWidth => "unicode-width",
|
||||
};
|
||||
|
||||
write!(f, "{s}")
|
||||
@ -297,6 +307,7 @@ fn test_one_newline() {
|
||||
correct_counts.insert(Counter::GraphemeClusters, 1);
|
||||
correct_counts.insert(Counter::Bytes, 1);
|
||||
correct_counts.insert(Counter::CodePoints, 1);
|
||||
correct_counts.insert(Counter::UnicodeWidth, 0);
|
||||
|
||||
assert_eq!(correct_counts, counts);
|
||||
}
|
||||
@ -336,6 +347,7 @@ fn test_count_counts_lines() {
|
||||
|
||||
// one more than grapheme clusters because of \r\n
|
||||
correct_counts.insert(Counter::CodePoints, 24);
|
||||
correct_counts.insert(Counter::UnicodeWidth, 17);
|
||||
|
||||
assert_eq!(correct_counts, counts);
|
||||
}
|
||||
@ -353,6 +365,7 @@ fn test_count_counts_words() {
|
||||
correct_counts.insert(Counter::Bytes, i_can_eat_glass.len());
|
||||
correct_counts.insert(Counter::Words, 9);
|
||||
correct_counts.insert(Counter::CodePoints, 50);
|
||||
correct_counts.insert(Counter::UnicodeWidth, 50);
|
||||
|
||||
assert_eq!(correct_counts, counts);
|
||||
}
|
||||
|
@ -93,6 +93,6 @@ impl Command for Complete {
|
||||
}
|
||||
|
||||
fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
|
||||
(Some(OutDest::Capture), Some(OutDest::Capture))
|
||||
(Some(OutDest::PipeSeparate), Some(OutDest::PipeSeparate))
|
||||
}
|
||||
}
|
||||
|
@ -180,12 +180,19 @@ impl Command for External {
|
||||
}
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = ChildProcess::new(
|
||||
let mut child = ChildProcess::new(
|
||||
child,
|
||||
merged_stream,
|
||||
matches!(stderr, OutDest::Pipe),
|
||||
call.head,
|
||||
)?;
|
||||
|
||||
if matches!(stdout, OutDest::Pipe | OutDest::PipeSeparate)
|
||||
|| matches!(stderr, OutDest::Pipe | OutDest::PipeSeparate)
|
||||
{
|
||||
child.ignore_error(true);
|
||||
}
|
||||
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::child(child, call.head),
|
||||
None,
|
||||
@ -340,7 +347,7 @@ fn write_pipeline_data(
|
||||
} else if let PipelineData::Value(Value::Binary { val, .. }, ..) = data {
|
||||
writer.write_all(&val)?;
|
||||
} else {
|
||||
stack.start_capture();
|
||||
stack.start_collect_value();
|
||||
|
||||
// Turn off color as we pass data through
|
||||
Arc::make_mut(&mut engine_state.config).use_ansi_coloring = false;
|
||||
@ -367,7 +374,7 @@ pub fn command_not_found(
|
||||
) -> ShellError {
|
||||
// Run the `command_not_found` hook if there is one.
|
||||
if let Some(hook) = &stack.get_config(engine_state).hooks.command_not_found {
|
||||
let mut stack = stack.start_capture();
|
||||
let mut stack = stack.start_collect_value();
|
||||
// Set a special environment variable to avoid infinite loops when the
|
||||
// `command_not_found` hook triggers itself.
|
||||
let canary = "ENTERED_COMMAND_NOT_FOUND";
|
||||
@ -623,8 +630,16 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_write_pipeline_data() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut engine_state = EngineState::new();
|
||||
let stack = Stack::new();
|
||||
let cwd = std::env::current_dir()
|
||||
.unwrap()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
// set the PWD environment variable as it's required now
|
||||
engine_state.add_env_var("PWD".into(), Value::string(cwd, Span::test_data()));
|
||||
|
||||
let mut buf = vec![];
|
||||
let input = PipelineData::Empty;
|
||||
|
@ -69,7 +69,7 @@ prints out the list properly."#
|
||||
let icons_param: bool = call.has_flag(engine_state, stack, "icons")?;
|
||||
let config = &stack.get_config(engine_state);
|
||||
let env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||
Some(v) => Some(env_to_string("LS_COLORS", &v, engine_state, stack)?),
|
||||
Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
@ -237,7 +237,9 @@ fn create_grid_output(
|
||||
cell.alignment = Alignment::Left;
|
||||
grid.add(cell);
|
||||
} else {
|
||||
let style = ls_colors.style_for_path(value.clone());
|
||||
let no_ansi = nu_utils::strip_ansi_unlikely(&value);
|
||||
let path = cwd.join(no_ansi.as_ref());
|
||||
let style = ls_colors.style_for_path(path.clone());
|
||||
let ansi_style = style.map(Style::to_nu_ansi_term_style).unwrap_or_default();
|
||||
let mut cell = Cell::from(ansi_style.paint(value).to_string());
|
||||
cell.alignment = Alignment::Left;
|
||||
|
@ -201,6 +201,7 @@ pub fn icon_for_file(file_path: &Path, span: Span) -> Result<char, ShellError> {
|
||||
"cc" => '\u{e61d}', //
|
||||
"cert" => '\u{eafa}', //
|
||||
"cfg" => '\u{e615}', //
|
||||
"cjs" => '\u{e74e}', //
|
||||
"class" => '\u{e256}', //
|
||||
"clj" => '\u{e768}', //
|
||||
"cljs" => '\u{e76a}', //
|
||||
|
@ -5,6 +5,7 @@
|
||||
use lscolors::{LsColors, Style};
|
||||
use nu_color_config::{color_from_hex, StyleComputer, TextStyle};
|
||||
use nu_engine::{command_prelude::*, env_to_string};
|
||||
use nu_path::form::Absolute;
|
||||
use nu_pretty_hex::HexConfig;
|
||||
use nu_protocol::{
|
||||
ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator,
|
||||
@ -125,7 +126,7 @@ impl Command for Table {
|
||||
let val = Value::list(supported_table_modes(), Span::test_data());
|
||||
return Ok(val.into_pipeline_data());
|
||||
}
|
||||
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let cfg = parse_table_config(call, engine_state, stack)?;
|
||||
let input = CmdInput::new(engine_state, stack, call, input);
|
||||
|
||||
@ -135,7 +136,7 @@ impl Command for Table {
|
||||
let _ = nu_utils::enable_vt_processing();
|
||||
}
|
||||
|
||||
handle_table_command(input, cfg)
|
||||
handle_table_command(input, cfg, cwd)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -367,6 +368,7 @@ impl<'a> CmdInput<'a> {
|
||||
fn handle_table_command(
|
||||
mut input: CmdInput<'_>,
|
||||
cfg: TableConfig,
|
||||
cwd: nu_path::PathBuf<Absolute>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = input.data.span().unwrap_or(input.call.head);
|
||||
match input.data {
|
||||
@ -389,11 +391,11 @@ fn handle_table_command(
|
||||
let stream = ListStream::new(vals.into_iter(), span, signals);
|
||||
input.data = PipelineData::Empty;
|
||||
|
||||
handle_row_stream(input, cfg, stream, metadata)
|
||||
handle_row_stream(input, cfg, stream, metadata, cwd)
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
input.data = PipelineData::Empty;
|
||||
handle_row_stream(input, cfg, stream, metadata)
|
||||
handle_row_stream(input, cfg, stream, metadata, cwd)
|
||||
}
|
||||
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
||||
input.data = PipelineData::Empty;
|
||||
@ -413,7 +415,7 @@ fn handle_table_command(
|
||||
let stream =
|
||||
ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals);
|
||||
input.data = PipelineData::Empty;
|
||||
handle_row_stream(input, cfg, stream, metadata)
|
||||
handle_row_stream(input, cfg, stream, metadata, cwd)
|
||||
}
|
||||
x => Ok(x),
|
||||
}
|
||||
@ -605,6 +607,7 @@ fn handle_row_stream(
|
||||
cfg: TableConfig,
|
||||
stream: ListStream,
|
||||
metadata: Option<PipelineMetadata>,
|
||||
cwd: nu_path::PathBuf<Absolute>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let stream = match metadata.as_ref() {
|
||||
// First, `ls` sources:
|
||||
@ -620,7 +623,7 @@ fn handle_row_stream(
|
||||
let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") {
|
||||
Some(v) => Some(env_to_string(
|
||||
"LS_COLORS",
|
||||
&v,
|
||||
v,
|
||||
input.engine_state,
|
||||
input.stack,
|
||||
)?),
|
||||
@ -634,7 +637,9 @@ fn handle_row_stream(
|
||||
if let Some(value) = record.to_mut().get_mut("name") {
|
||||
let span = value.span();
|
||||
if let Value::String { val, .. } = value {
|
||||
if let Some(val) = render_path_name(val, &config, &ls_colors, span) {
|
||||
if let Some(val) =
|
||||
render_path_name(val, &config, &ls_colors, cwd.clone(), span)
|
||||
{
|
||||
*value = val;
|
||||
}
|
||||
}
|
||||
@ -1008,15 +1013,16 @@ fn render_path_name(
|
||||
path: &str,
|
||||
config: &Config,
|
||||
ls_colors: &LsColors,
|
||||
cwd: nu_path::PathBuf<Absolute>,
|
||||
span: Span,
|
||||
) -> Option<Value> {
|
||||
if !config.ls.use_ls_colors {
|
||||
return None;
|
||||
}
|
||||
|
||||
let fullpath = cwd.join(path);
|
||||
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
||||
|
||||
let metadata = std::fs::symlink_metadata(stripped_path.as_ref());
|
||||
let metadata = std::fs::symlink_metadata(fullpath);
|
||||
let has_metadata = metadata.is_ok();
|
||||
let style =
|
||||
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), metadata.ok().as_ref());
|
||||
|
@ -2,18 +2,18 @@ use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn canonical() {
|
||||
super::test_canonical("new-base64");
|
||||
super::test_canonical("new-base64 --url");
|
||||
super::test_canonical("new-base64 --nopad");
|
||||
super::test_canonical("new-base64 --url --nopad");
|
||||
super::test_canonical("base64");
|
||||
super::test_canonical("base64 --url");
|
||||
super::test_canonical("base64 --nopad");
|
||||
super::test_canonical("base64 --url --nopad");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_() {
|
||||
super::test_const("new-base64");
|
||||
super::test_const("new-base64 --url");
|
||||
super::test_const("new-base64 --nopad");
|
||||
super::test_const("new-base64 --url --nopad");
|
||||
super::test_const("base64");
|
||||
super::test_const("base64 --url");
|
||||
super::test_const("base64 --nopad");
|
||||
super::test_const("base64 --url --nopad");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -21,7 +21,7 @@ fn encode() {
|
||||
let text = "Ș̗͙̂̏o̲̲̗͗̌͊m̝̊̓́͂ë̡̦̞̤́̌̈́̀ ̥̝̪̎̿ͅf̧̪̻͉͗̈́̍̆u̮̝͌̈́ͅn̹̞̈́̊k̮͇̟͎̂͘y̧̲̠̾̆̕ͅ ̙͖̭͔̂̐t̞́́͘e̢̨͕̽x̥͋t͍̑̔͝";
|
||||
let encoded = "U8yCzI/MpsyXzZlvzZfMjM2KzLLMssyXbcyKzJPMgc2CzJ1lzYTMjM2EzIDMpsyhzJ7MpCDMjsy/zYXMpcydzKpmzZfNhMyNzIbMqsy7zKfNiXXNjM2EzK7Mnc2Fbs2EzIrMucyea82YzILMrs2HzJ/NjnnMvsyVzIbNhcyyzKfMoCDMgsyQzJnNlsytzZR0zIHNmMyBzJ5lzL3Mos2VzKh4zYvMpXTMkcyUzZ3NjQ==";
|
||||
|
||||
let outcome = nu!("'{}' | encode new-base64", text);
|
||||
let outcome = nu!("'{}' | encode base64", text);
|
||||
assert_eq!(outcome.out, encoded);
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ fn decode_string() {
|
||||
let text = "Very important data";
|
||||
let encoded = "VmVyeSBpbXBvcnRhbnQgZGF0YQ==";
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 | decode", encoded);
|
||||
let outcome = nu!("'{}' | decode base64 | decode", encoded);
|
||||
assert_eq!(outcome.out, text);
|
||||
}
|
||||
|
||||
@ -40,10 +40,10 @@ fn decode_pad_nopad() {
|
||||
let encoded_pad = "4oCdwqUuw6RAwrBiWsO2wqI=";
|
||||
let encoded_nopad = "4oCdwqUuw6RAwrBiWsO2wqI";
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 | decode", encoded_pad);
|
||||
let outcome = nu!("'{}' | decode base64 | decode", encoded_pad);
|
||||
assert_eq!(outcome.out, text);
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 --nopad | decode", encoded_nopad);
|
||||
let outcome = nu!("'{}' | decode base64 --nopad | decode", encoded_nopad);
|
||||
assert_eq!(outcome.out, text);
|
||||
}
|
||||
|
||||
@ -53,10 +53,10 @@ fn decode_url() {
|
||||
let encoded = "cDpn15jdvt+rdCs/";
|
||||
let encoded_url = "cDpn15jdvt-rdCs_";
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 | decode", encoded);
|
||||
let outcome = nu!("'{}' | decode base64 | decode", encoded);
|
||||
assert_eq!(outcome.out, text);
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 --url | decode", encoded_url);
|
||||
let outcome = nu!("'{}' | decode base64 --url | decode", encoded_url);
|
||||
assert_eq!(outcome.out, text);
|
||||
}
|
||||
|
||||
@ -65,9 +65,9 @@ fn reject_pad_nopad() {
|
||||
let encoded_nopad = "YQ";
|
||||
let encoded_pad = "YQ==";
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64", encoded_nopad);
|
||||
let outcome = nu!("'{}' | decode base64", encoded_nopad);
|
||||
assert!(!outcome.err.is_empty());
|
||||
|
||||
let outcome = nu!("'{}' | decode new-base64 --nopad", encoded_pad);
|
||||
let outcome = nu!("'{}' | decode base64 --nopad", encoded_pad);
|
||||
assert!(!outcome.err.is_empty())
|
||||
}
|
||||
|
@ -9,25 +9,28 @@ fn capture_errors_works() {
|
||||
assert!(actual.err.contains("column_not_found"));
|
||||
}
|
||||
|
||||
// TODO: need to add tests under display_error.exit_code = true
|
||||
#[test]
|
||||
fn capture_errors_works_for_external() {
|
||||
let actual = nu!("do -c {nu --testbin fail}");
|
||||
assert!(actual.err.contains("exited with code"));
|
||||
assert_eq!(actual.out, "");
|
||||
assert!(!actual.status.success());
|
||||
assert!(!actual.err.contains("exited with code"));
|
||||
}
|
||||
|
||||
// TODO: need to add tests under display_error.exit_code = true
|
||||
#[test]
|
||||
fn capture_errors_works_for_external_with_pipeline() {
|
||||
let actual = nu!("do -c {nu --testbin fail} | echo `text`");
|
||||
assert!(actual.err.contains("exited with code"));
|
||||
assert_eq!(actual.out, "");
|
||||
assert!(!actual.status.success());
|
||||
assert!(!actual.err.contains("exited with code"));
|
||||
}
|
||||
|
||||
// TODO: need to add tests under display_error.exit_code = true
|
||||
#[test]
|
||||
fn capture_errors_works_for_external_with_semicolon() {
|
||||
let actual = nu!(r#"do -c {nu --testbin fail}; echo `text`"#);
|
||||
assert!(actual.err.contains("exited with code"));
|
||||
assert_eq!(actual.out, "");
|
||||
assert!(!actual.status.success());
|
||||
assert!(!actual.err.contains("exited with code"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -73,3 +76,10 @@ fn run_closure_with_it_using() {
|
||||
assert!(actual.err.is_empty());
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waits_for_external() {
|
||||
let actual = nu!(r#"do -p { nu -c 'sleep 1sec; print before; exit 1'}; print after"#);
|
||||
assert!(actual.err.is_empty());
|
||||
assert_eq!(actual.out, "beforeafter");
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ fn each_works_separately() {
|
||||
|
||||
#[test]
|
||||
fn each_group_works() {
|
||||
let actual = nu!("echo [1 2 3 4 5 6] | group 3 | to json --raw");
|
||||
let actual = nu!("echo [1 2 3 4 5 6] | chunks 3 | to json --raw");
|
||||
|
||||
assert_eq!(actual.out, "[[1,2,3],[4,5,6]]");
|
||||
}
|
||||
|
@ -10,43 +10,48 @@ fn base64_defaults_to_encoding_with_standard_character_type() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_characterset_binhex() {
|
||||
fn base64_defaults_to_encoding_with_nopad() {
|
||||
let actual = nu!(r#"
|
||||
echo 'username:password' | encode base64 --character-set binhex
|
||||
echo 'username:password' | encode base64 --nopad
|
||||
"#);
|
||||
|
||||
assert_eq!(actual.out, "GA0PFQjKE@8kF'&cFhG[FQ3");
|
||||
assert_eq!(actual.out, "dXNlcm5hbWU6cGFzc3dvcmQ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_when_invalid_character_set_given() {
|
||||
fn base64_decode_value() {
|
||||
let actual = nu!(r#"
|
||||
echo 'username:password' | encode base64 --character-set 'this is invalid'
|
||||
echo 'YWJjeHl6' | decode base64 | decode
|
||||
"#);
|
||||
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("this is invalid is not a valid character-set"));
|
||||
assert_eq!(actual.out, "abcxyz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_characterset_binhex() {
|
||||
let actual = nu!(
|
||||
r#""GA0PFQjKE@8kF'&cFhG[FQ3" | decode base64 --character-set binhex --binary | decode utf-8"#
|
||||
);
|
||||
fn base64_decode_with_nopad() {
|
||||
let actual = nu!(r#"
|
||||
echo 'R29vZCBsdWNrIHRvIHlvdQ' | decode base64 --nopad | decode
|
||||
"#);
|
||||
|
||||
assert_eq!(actual.out, "username:password");
|
||||
assert_eq!(actual.out, "Good luck to you");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_with_url() {
|
||||
let actual = nu!(r#"
|
||||
echo 'vu7_' | decode base64 --url | decode
|
||||
"#);
|
||||
|
||||
assert_eq!(actual.out, "¾îÿ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_invalid_decode_value() {
|
||||
let actual = nu!(r#"
|
||||
echo "this should not be a valid encoded value" | decode base64 --character-set url-safe
|
||||
echo "this should not be a valid encoded value" | decode base64
|
||||
"#);
|
||||
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("invalid base64 input for character set url-safe"));
|
||||
assert!(actual.err.contains("nu::shell::incorrect_value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user