Merge branch 'main' into ir
This commit is contained in:
commit
4fe74e3f8c
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
# Prevent sudden announcement of a new advisory from failing ci:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: rustsec/audit-check@v1.4.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -33,10 +33,10 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
@ -66,10 +66,10 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
||||
|
@ -95,10 +95,10 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Install Nushell
|
||||
run: cargo install --path . --locked --no-default-features
|
||||
|
@ -146,10 +146,10 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||
|
|
8
.github/workflows/nightly-build.yml
vendored
8
.github/workflows/nightly-build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
# if: github.repository == 'nushell/nightly'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
ref: main
|
||||
|
@ -112,7 +112,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
@ -122,7 +122,7 @@ jobs:
|
|||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
rustflags: ''
|
||||
|
@ -181,7 +181,7 @@ jobs:
|
|||
- name: Waiting for Release
|
||||
run: sleep 1800
|
||||
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
ref: main
|
||||
|
||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -62,14 +62,14 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- 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.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
|
|
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.22.1
|
||||
uses: crate-ci/typos@v1.22.7
|
||||
|
|
167
Cargo.lock
generated
167
Cargo.lock
generated
|
@ -920,6 +920,15 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
@ -1218,6 +1227,12 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "doctest-file"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
|
@ -1678,9 +1693,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.18.3"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
|
||||
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
|
@ -2017,10 +2032,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4d0250d41da118226e55b3d50ca3f0d9e0a0f6829b92f543ac0054aeea1572"
|
||||
checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3"
|
||||
dependencies = [
|
||||
"doctest-file",
|
||||
"libc",
|
||||
"recvmsg",
|
||||
"widestring",
|
||||
|
@ -2279,9 +2295,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.16.2+1.7.2"
|
||||
version = "0.17.0+1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
||||
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -2753,7 +2769,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm",
|
||||
|
@ -2806,7 +2822,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
|
@ -2841,7 +2857,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
|
@ -2853,7 +2869,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck 0.5.0",
|
||||
|
@ -2878,7 +2894,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2890,7 +2906,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2901,7 +2917,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
|
@ -2913,7 +2929,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
|
@ -3011,7 +3027,7 @@ dependencies = [
|
|||
"uu_mv",
|
||||
"uu_uname",
|
||||
"uu_whoami",
|
||||
"uucore 0.0.25",
|
||||
"uucore",
|
||||
"uuid",
|
||||
"v_htmlescape",
|
||||
"wax",
|
||||
|
@ -3020,9 +3036,20 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-derive-value"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-glob",
|
||||
|
@ -3033,7 +3060,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"anyhow",
|
||||
|
@ -3058,14 +3085,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"num-traits",
|
||||
|
@ -3075,7 +3102,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
|
@ -3096,7 +3123,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
|
@ -3112,7 +3139,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"omnipath",
|
||||
|
@ -3121,7 +3148,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
|
@ -3136,7 +3163,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-core"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"log",
|
||||
|
@ -3150,7 +3177,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-engine",
|
||||
|
@ -3165,7 +3192,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nu-protocol",
|
||||
|
@ -3177,7 +3204,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-lang",
|
||||
|
@ -3195,7 +3222,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
|
@ -3204,17 +3231,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"byte-unit",
|
||||
"chrono",
|
||||
"chrono-humanize",
|
||||
"convert_case",
|
||||
"fancy-regex",
|
||||
"indexmap",
|
||||
"lru",
|
||||
"miette",
|
||||
"nix",
|
||||
"nu-derive-value",
|
||||
"nu-path",
|
||||
"nu-system",
|
||||
"nu-test-support",
|
||||
|
@ -3235,7 +3264,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
|
@ -3246,7 +3275,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.12.1",
|
||||
|
@ -3264,7 +3293,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
|
@ -3278,7 +3307,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
|
@ -3286,7 +3315,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
|
@ -3298,7 +3327,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
|
@ -3324,7 +3353,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
|
@ -3334,7 +3363,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"eml-parser",
|
||||
"ical",
|
||||
|
@ -3347,7 +3376,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
|
@ -3356,7 +3385,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
|
@ -3365,7 +3394,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_polars"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz 0.9.0",
|
||||
|
@ -3396,7 +3425,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
|
@ -3408,7 +3437,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"serde",
|
||||
|
@ -3534,7 +3563,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "nuon"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
|
@ -6353,93 +6382,77 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|||
|
||||
[[package]]
|
||||
name = "uu_cp"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56"
|
||||
checksum = "c31fc5c95f7668999e129464a29e9080f69ba01ccf7a0ae43ff2cfdb15baa340"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"filetime",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"quick-error 2.0.1",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
"walkdir",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mkdir"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae"
|
||||
checksum = "496d95e0e3121e4d424ba62019eb84a6f1102213ca8ca16c0a2f8c652c7236c3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mktemp"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57"
|
||||
checksum = "a28a0d9744bdc28ceaf13f70b959bacded91aedfd008402d72fa1e3224158653"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"rand",
|
||||
"tempfile",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mv"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022"
|
||||
checksum = "53680908b01c5ac3cc0ee8a376de3e51a36dde2c5a5227a115a3d0977cc4539b"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fs_extra",
|
||||
"indicatif",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_uname"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5"
|
||||
checksum = "a7f4125fb4f286313bca8f222abaefe39db54d65179ea788c91ebd3162345f4e"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"platform-info",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_whoami"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4"
|
||||
checksum = "7f7b313901a15cfde2d88f434fcd077903d690f73cc36d1cec20f47906960aec"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
"uucore_procs",
|
||||
"wild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.0.26"
|
||||
|
|
67
Cargo.toml
67
Cargo.toml
|
@ -11,7 +11,7 @@ license = "MIT"
|
|||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.77.2"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -39,6 +39,7 @@ members = [
|
|||
"crates/nu-lsp",
|
||||
"crates/nu-pretty-hex",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-derive-value",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu-plugin-core",
|
||||
"crates/nu-plugin-engine",
|
||||
|
@ -74,6 +75,7 @@ chardetng = "0.1.17"
|
|||
chrono = { default-features = false, version = "0.4.34" }
|
||||
chrono-humanize = "0.2.3"
|
||||
chrono-tz = "0.8"
|
||||
convert_case = "0.6"
|
||||
crossbeam-channel = "0.5.8"
|
||||
crossterm = "0.27"
|
||||
csv = "1.3"
|
||||
|
@ -93,7 +95,7 @@ heck = "0.5.0"
|
|||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.2"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.1.0"
|
||||
interprocess = "2.2.0"
|
||||
is_executable = "1.0"
|
||||
itertools = "0.12"
|
||||
libc = "0.2"
|
||||
|
@ -123,11 +125,14 @@ pathdiff = "0.2"
|
|||
percent-encoding = "2"
|
||||
pretty_assertions = "1.4"
|
||||
print-positions = "0.6"
|
||||
proc-macro-error = { version = "1.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
procfs = "0.16.0"
|
||||
pwd = "1.3"
|
||||
quick-xml = "0.31.0"
|
||||
quickcheck = "1.0"
|
||||
quickcheck_macros = "1.0"
|
||||
quote = "1.0"
|
||||
rand = "0.8"
|
||||
ratatui = "0.26"
|
||||
rayon = "1.10"
|
||||
|
@ -147,6 +152,7 @@ serde_urlencoded = "0.7.1"
|
|||
serde_yaml = "0.9"
|
||||
sha2 = "0.10"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
syn = "2.0"
|
||||
sysinfo = "0.30"
|
||||
tabled = { version = "0.14.0", default-features = false }
|
||||
tempfile = "3.10"
|
||||
|
@ -159,13 +165,13 @@ unicode-segmentation = "1.11"
|
|||
unicode-width = "0.1"
|
||||
ureq = { version = "2.9", default-features = false }
|
||||
url = "2.2"
|
||||
uu_cp = "0.0.25"
|
||||
uu_mkdir = "0.0.25"
|
||||
uu_mktemp = "0.0.25"
|
||||
uu_mv = "0.0.25"
|
||||
uu_whoami = "0.0.25"
|
||||
uu_uname = "0.0.25"
|
||||
uucore = "0.0.25"
|
||||
uu_cp = "0.0.26"
|
||||
uu_mkdir = "0.0.26"
|
||||
uu_mktemp = "0.0.26"
|
||||
uu_mv = "0.0.26"
|
||||
uu_whoami = "0.0.26"
|
||||
uu_uname = "0.0.26"
|
||||
uucore = "0.0.26"
|
||||
uuid = "1.8.0"
|
||||
v_htmlescape = "0.15.0"
|
||||
wax = "0.6"
|
||||
|
@ -174,27 +180,28 @@ windows = "0.54"
|
|||
winreg = "0.52"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.3" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.3", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.3" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.94.3" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.94.3" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.94.3" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.3" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.3" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.3" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.94.3" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.94.3" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.94.3" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.95.1" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.95.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.95.1" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.95.1" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.95.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.95.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
|
||||
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
ctrlc = { workspace = true }
|
||||
dirs-next = { workspace = true }
|
||||
log = { workspace = true }
|
||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||
|
@ -218,9 +225,9 @@ nix = { workspace = true, default-features = false, features = [
|
|||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.3" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.3" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" }
|
||||
assert_cmd = "2.0"
|
||||
dirs-next = { workspace = true }
|
||||
tango-bench = "0.5"
|
||||
|
@ -244,7 +251,6 @@ default = ["default-no-clipboard", "system-clipboard"]
|
|||
# See https://github.com/nushell/nushell/pull/11535
|
||||
default-no-clipboard = [
|
||||
"plugin",
|
||||
"which-support",
|
||||
"trash-support",
|
||||
"sqlite",
|
||||
"mimalloc",
|
||||
|
@ -264,7 +270,6 @@ system-clipboard = [
|
|||
]
|
||||
|
||||
# Stable (Default)
|
||||
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
|
||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||
|
||||
# SQLite commands for nushell
|
||||
|
@ -305,4 +310,4 @@ bench = false
|
|||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
harness = false
|
|
@ -52,7 +52,7 @@ To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/ac
|
|||
|
||||
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
|
||||
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).
|
||||
|
||||
|
@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
|
|||
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
||||
- [Dorothy](http://github.com/bevry/dorothy)
|
||||
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||
- [x-cmd](https://x-cmd.com/mod/nu)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
|||
&mut engine,
|
||||
&mut stack,
|
||||
PipelineData::empty(),
|
||||
None,
|
||||
false,
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -90,8 +89,7 @@ fn bench_command(
|
|||
&mut engine,
|
||||
&mut stack,
|
||||
PipelineData::empty(),
|
||||
None,
|
||||
false,
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
|
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -46,4 +46,4 @@ which = { workspace = true }
|
|||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
|
@ -344,7 +344,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||
name: identity.name().to_owned(),
|
||||
filename: identity.filename().to_owned(),
|
||||
shell: identity.shell().map(|p| p.to_owned()),
|
||||
data: PluginRegistryItemData::Valid { commands },
|
||||
data: PluginRegistryItemData::Valid {
|
||||
metadata: Default::default(),
|
||||
commands,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,45 @@ use nu_protocol::{
|
|||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EvaluateCommandsOpts {
|
||||
pub table_mode: Option<Value>,
|
||||
pub error_style: Option<Value>,
|
||||
pub no_newline: bool,
|
||||
}
|
||||
|
||||
/// Run a command (or commands) given to us by the user
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
table_mode: Option<Value>,
|
||||
no_newline: bool,
|
||||
opts: EvaluateCommandsOpts,
|
||||
) -> Result<(), ShellError> {
|
||||
let EvaluateCommandsOpts {
|
||||
table_mode,
|
||||
error_style,
|
||||
no_newline,
|
||||
} = opts;
|
||||
|
||||
// Handle the configured error style early
|
||||
if let Some(e_style) = error_style {
|
||||
match e_style.coerce_str()?.parse() {
|
||||
Ok(e_style) => {
|
||||
Arc::make_mut(&mut engine_state.config).error_style = e_style;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Invalid value for `--error-style`".into(),
|
||||
msg: err.into(),
|
||||
span: Some(e_style.span()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
convert_env_values(engine_state, stack)?;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ mod validation;
|
|||
pub use commands::add_cli_context;
|
||||
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use eval_cmds::evaluate_commands;
|
||||
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;
|
||||
|
|
|
@ -75,7 +75,7 @@ const DEFAULT_HELP_MENU: &str = r#"
|
|||
// Adds all menus to line editor
|
||||
pub(crate) fn add_menus(
|
||||
mut line_editor: Reedline,
|
||||
engine_state: Arc<EngineState>,
|
||||
engine_state_ref: Arc<EngineState>,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
|
@ -83,7 +83,7 @@ pub(crate) fn add_menus(
|
|||
line_editor = line_editor.clear_menus();
|
||||
|
||||
for menu in &config.menus {
|
||||
line_editor = add_menu(line_editor, menu, engine_state.clone(), stack, config)?
|
||||
line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)?
|
||||
}
|
||||
|
||||
// Checking if the default menus have been added from the config file
|
||||
|
@ -93,13 +93,16 @@ pub(crate) fn add_menus(
|
|||
("help_menu", DEFAULT_HELP_MENU),
|
||||
];
|
||||
|
||||
let mut engine_state = (*engine_state_ref).clone();
|
||||
let mut menu_eval_results = vec![];
|
||||
|
||||
for (name, definition) in default_menus {
|
||||
if !config
|
||||
.menus
|
||||
.iter()
|
||||
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||
{
|
||||
let (block, _) = {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let output = parse(
|
||||
&mut working_set,
|
||||
|
@ -111,15 +114,31 @@ pub(crate) fn add_menus(
|
|||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let mut temp_stack = Stack::new().capture();
|
||||
let input = PipelineData::Empty;
|
||||
let res = eval_block::<WithoutDebug>(&engine_state, &mut temp_stack, &block, input)?;
|
||||
menu_eval_results.push(eval_block::<WithoutDebug>(
|
||||
&engine_state,
|
||||
&mut temp_stack,
|
||||
&block,
|
||||
input,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
if let PipelineData::Value(value, None) = res {
|
||||
for menu in create_menus(&value)? {
|
||||
line_editor =
|
||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||
}
|
||||
let new_engine_state_ref = Arc::new(engine_state);
|
||||
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ impl Highlighter for NuHighlighter {
|
|||
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
|
@ -452,15 +453,17 @@ fn find_matching_block_end_in_expr(
|
|||
}
|
||||
}
|
||||
|
||||
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}),
|
||||
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
|
||||
exprs.iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
Expr::List(list) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
|
|
|
@ -763,11 +763,13 @@ fn variables_completions() {
|
|||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(15, suggestions.len());
|
||||
assert_eq!(18, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"cache-dir".into(),
|
||||
"config-path".into(),
|
||||
"current-exe".into(),
|
||||
"data-dir".into(),
|
||||
"default-config-dir".into(),
|
||||
"env-path".into(),
|
||||
"history-enabled".into(),
|
||||
|
@ -781,6 +783,7 @@ fn variables_completions() {
|
|||
"plugin-path".into(),
|
||||
"startup-time".into(),
|
||||
"temp-path".into(),
|
||||
"vendor-autoload-dir".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
|
|
|
@ -5,17 +5,17 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -194,7 +194,7 @@ pub fn eval_hook(
|
|||
let Some(follow) = val.get("code") else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: "code".into(),
|
||||
span,
|
||||
span: Some(span),
|
||||
src_span: span,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -20,13 +20,14 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
|
|||
|
||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||
|
||||
/// Returns a inclusive pair of boundary in given `range`.
|
||||
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
||||
match range {
|
||||
Range::IntRange(range) => {
|
||||
let start = range.start().try_into().unwrap_or(0);
|
||||
let end = match range.end() {
|
||||
Bound::Included(v) => (v + 1) as isize,
|
||||
Bound::Excluded(v) => v as isize,
|
||||
Bound::Included(v) => v as isize,
|
||||
Bound::Excluded(v) => (v - 1) as isize,
|
||||
Bound::Unbounded => isize::MAX,
|
||||
};
|
||||
Ok((start, end))
|
||||
|
|
|
@ -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.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,13 +13,13 @@ version = "0.94.3"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-json = { version = "0.94.3", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-pretty-hex = { version = "0.94.3", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-json = { version = "0.95.1", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
|
@ -32,11 +32,7 @@ serde_urlencoded = { workspace = true }
|
|||
v_htmlescape = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[features]
|
||||
extra = ["default"]
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -1,5 +1,4 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_expression};
|
||||
use nu_parser::parse_expression;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -57,14 +56,7 @@ impl Command for FormatPattern {
|
|||
string_span.start + 1,
|
||||
)?;
|
||||
|
||||
format(
|
||||
input_val,
|
||||
&ops,
|
||||
engine_state,
|
||||
&mut working_set,
|
||||
stack,
|
||||
call.head,
|
||||
)
|
||||
format(input_val, &ops, engine_state, call.head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +92,6 @@ enum FormatOperation {
|
|||
FixedText(String),
|
||||
// raw input is something like {column1.column2}
|
||||
ValueFromColumn(String, Span),
|
||||
// raw input is something like {$it.column1.column2} or {$var}.
|
||||
ValueNeedEval(String, Span),
|
||||
}
|
||||
|
||||
/// Given a pattern that is fed into the Format command, we can process it and subdivide it
|
||||
|
@ -110,7 +100,6 @@ enum FormatOperation {
|
|||
/// there without any further processing.
|
||||
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
||||
/// formatted according to the input pattern.
|
||||
/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form:
|
||||
/// "$it.column1.column2" or "$variable"
|
||||
fn extract_formatting_operations(
|
||||
input: String,
|
||||
|
@ -161,10 +150,17 @@ fn extract_formatting_operations(
|
|||
|
||||
if !column_name.is_empty() {
|
||||
if column_need_eval {
|
||||
output.push(FormatOperation::ValueNeedEval(
|
||||
column_name.clone(),
|
||||
Span::new(span_start + column_span_start, span_start + column_span_end),
|
||||
));
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Removed functionality".into(),
|
||||
msg: "The ability to use variables ($it) in `format pattern` has been removed."
|
||||
.into(),
|
||||
span: Some(error_span),
|
||||
help: Some(
|
||||
"You can use other formatting options, such as string interpolation."
|
||||
.into(),
|
||||
),
|
||||
inner: vec![],
|
||||
});
|
||||
} else {
|
||||
output.push(FormatOperation::ValueFromColumn(
|
||||
column_name.clone(),
|
||||
|
@ -185,8 +181,6 @@ fn format(
|
|||
input_data: Value,
|
||||
format_operations: &[FormatOperation],
|
||||
engine_state: &EngineState,
|
||||
working_set: &mut StateWorkingSet,
|
||||
stack: &mut Stack,
|
||||
head_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let data_as_value = input_data;
|
||||
|
@ -194,13 +188,7 @@ fn format(
|
|||
// We can only handle a Record or a List of Records
|
||||
match data_as_value {
|
||||
Value::Record { .. } => {
|
||||
match format_record(
|
||||
format_operations,
|
||||
&data_as_value,
|
||||
engine_state,
|
||||
working_set,
|
||||
stack,
|
||||
) {
|
||||
match format_record(format_operations, &data_as_value, engine_state) {
|
||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||
Err(value) => Err(value),
|
||||
}
|
||||
|
@ -211,13 +199,7 @@ fn format(
|
|||
for val in vals.iter() {
|
||||
match val {
|
||||
Value::Record { .. } => {
|
||||
match format_record(
|
||||
format_operations,
|
||||
val,
|
||||
engine_state,
|
||||
working_set,
|
||||
stack,
|
||||
) {
|
||||
match format_record(format_operations, val, engine_state) {
|
||||
Ok(value) => {
|
||||
list.push(Value::string(value, head_span));
|
||||
}
|
||||
|
@ -256,12 +238,9 @@ fn format_record(
|
|||
format_operations: &[FormatOperation],
|
||||
data_as_value: &Value,
|
||||
engine_state: &EngineState,
|
||||
working_set: &mut StateWorkingSet,
|
||||
stack: &mut Stack,
|
||||
) -> Result<String, ShellError> {
|
||||
let config = engine_state.get_config();
|
||||
let mut output = String::new();
|
||||
let eval_expression = get_eval_expression(engine_state);
|
||||
|
||||
for op in format_operations {
|
||||
match op {
|
||||
|
@ -283,23 +262,6 @@ fn format_record(
|
|||
Err(se) => return Err(se),
|
||||
}
|
||||
}
|
||||
FormatOperation::ValueNeedEval(_col_name, span) => {
|
||||
let exp = parse_expression(working_set, &[*span]);
|
||||
match working_set.parse_errors.first() {
|
||||
None => {
|
||||
let parsed_result = eval_expression(engine_state, stack, &exp);
|
||||
if let Ok(val) = parsed_result {
|
||||
output.push_str(&val.to_abbreviated_string(config))
|
||||
}
|
||||
}
|
||||
Some(err) => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: format!("expression is invalid, detail message: {err:?}"),
|
||||
span: *span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
|
|
|
@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.28", default-features = false }
|
||||
|
@ -25,8 +25,7 @@ shadow-rs = { version = "0.28", default-features = false }
|
|||
|
||||
[features]
|
||||
mimalloc = []
|
||||
which-support = []
|
||||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
system-clipboard = []
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Break;
|
||||
|
@ -18,6 +19,15 @@ impl Command for Break {
|
|||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Continue;
|
||||
|
@ -18,6 +19,14 @@ impl Command for Continue {
|
|||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
|
|
@ -67,8 +67,8 @@ impl Command for Def {
|
|||
},
|
||||
Example {
|
||||
description: "Define a custom wrapper for an external command",
|
||||
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
||||
example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
|
||||
result: Some(Value::test_string("spam\tspam")),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -243,14 +243,24 @@ impl Command for Do {
|
|||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with a positional parameter",
|
||||
example: r#"do {|x| 100 + $x } 77"#,
|
||||
description: "Run the closure with a positional, type-checked parameter",
|
||||
example: r#"do {|x:int| 100 + $x } 77"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with input",
|
||||
example: r#"77 | do {|x| 100 + $in }"#,
|
||||
result: None, // TODO: returns 177
|
||||
description: "Run the closure with pipeline input",
|
||||
example: r#"77 | do { 100 + $in }"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure with a default parameter value",
|
||||
example: r#"77 | do {|x=100| $x + $in }"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure with two positional parameters",
|
||||
example: r#"do {|x,y| $x + $y } 77 100"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure and keep changes to the environment",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use nu_protocol::ParseWarning;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct For;
|
||||
|
@ -30,7 +31,7 @@ impl Command for For {
|
|||
.required("block", SyntaxShape::Block, "The block to run.")
|
||||
.switch(
|
||||
"numbered",
|
||||
"return a numbered item ($it.index and $it.item)",
|
||||
"DEPRECATED: return a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
.creates_scope()
|
||||
|
@ -79,6 +80,20 @@ impl Command for For {
|
|||
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
||||
|
||||
let numbered = call.has_flag(engine_state, stack, "numbered")?;
|
||||
if numbered {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ParseWarning::DeprecatedWarning {
|
||||
old_command: "--numbered/-n".into(),
|
||||
new_suggestion: "use `enumerate`".into(),
|
||||
span: call
|
||||
.get_named_arg("numbered")
|
||||
.expect("`get_named_arg` found `--numbered` but still failed")
|
||||
.span,
|
||||
url: "See `help for` examples".into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
|
@ -199,8 +214,7 @@ impl Command for For {
|
|||
},
|
||||
Example {
|
||||
description: "Number each item and print a message",
|
||||
example:
|
||||
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
|
||||
example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@ use nu_engine::{
|
|||
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::StateWorkingSet,
|
||||
engine::{CommandType, StateWorkingSet},
|
||||
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
|
||||
};
|
||||
|
||||
|
@ -41,6 +41,15 @@ impl Command for If {
|
|||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -124,6 +133,10 @@ impl Command for If {
|
|||
}
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["else", "conditional"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Loop;
|
||||
|
@ -20,6 +21,15 @@ impl Command for Loop {
|
|||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use nu_engine::{
|
||||
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
||||
};
|
||||
use nu_protocol::engine::Matcher;
|
||||
use nu_protocol::engine::{CommandType, Matcher};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Match;
|
||||
|
@ -27,6 +27,15 @@ impl Command for Match {
|
|||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Scope;
|
||||
|
@ -20,10 +19,6 @@ impl Command for Scope {
|
|||
"Commands for getting info about what is in scope."
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
|
||||
use nu_protocol::engine::Closure;
|
||||
use nu_protocol::engine::{Closure, CommandType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Try;
|
||||
|
@ -10,7 +10,7 @@ impl Command for Try {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Try to run a block, if it fails optionally run a catch block."
|
||||
"Try to run a block, if it fails optionally run a catch closure."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
|
@ -18,7 +18,7 @@ impl Command for Try {
|
|||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("try_block", SyntaxShape::Block, "Block to run.")
|
||||
.optional(
|
||||
"catch_block",
|
||||
"catch_closure",
|
||||
SyntaxShape::Keyword(
|
||||
b"catch".to_vec(),
|
||||
Box::new(SyntaxShape::OneOf(vec![
|
||||
|
@ -26,11 +26,20 @@ impl Command for Try {
|
|||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
])),
|
||||
),
|
||||
"Block to run if try block fails.",
|
||||
"Closure to run if try block fails.",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -86,9 +95,14 @@ impl Command for Try {
|
|||
},
|
||||
Example {
|
||||
description: "Try to run a missing command",
|
||||
example: "try { asdfasdf } catch { 'missing' } ",
|
||||
example: "try { asdfasdf } catch { 'missing' }",
|
||||
result: Some(Value::test_string("missing")),
|
||||
},
|
||||
Example {
|
||||
description: "Try to run a missing command and report the message",
|
||||
example: "try { asdfasdf } catch { |err| $err.msg }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
|||
Value::string(features_enabled().join(", "), span),
|
||||
);
|
||||
|
||||
// Get a list of plugin names
|
||||
// Get a list of plugin names and versions if present
|
||||
let installed_plugins = engine_state
|
||||
.plugins()
|
||||
.iter()
|
||||
.map(|x| x.identity().name())
|
||||
.map(|x| {
|
||||
let name = x.identity().name();
|
||||
if let Some(version) = x.metadata().and_then(|m| m.version) {
|
||||
format!("{name} {version}")
|
||||
} else {
|
||||
name.into()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
record.push(
|
||||
|
@ -160,11 +167,6 @@ fn features_enabled() -> Vec<String> {
|
|||
|
||||
// NOTE: There should be another way to know features on.
|
||||
|
||||
#[cfg(feature = "which-support")]
|
||||
{
|
||||
names.push("which".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
names.push("trash".to_string());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct While;
|
||||
|
@ -29,6 +30,15 @@ impl Command for While {
|
|||
vec!["loop"]
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -5,16 +5,16 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-cmd-plugin"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -118,11 +118,12 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
|||
},
|
||||
));
|
||||
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
|
||||
let metadata = interface.get_metadata()?;
|
||||
let commands = interface.get_signature()?;
|
||||
|
||||
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
||||
// Update the file with the received signatures
|
||||
let item = PluginRegistryItem::new(plugin.identity(), commands);
|
||||
// Update the file with the received metadata and signatures
|
||||
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
|
||||
contents.upsert_plugin(item);
|
||||
Ok(())
|
||||
})?;
|
||||
|
|
|
@ -16,6 +16,7 @@ impl Command for PluginList {
|
|||
Type::Table(
|
||||
[
|
||||
("name".into(), Type::String),
|
||||
("version".into(), Type::String),
|
||||
("is_running".into(), Type::Bool),
|
||||
("pid".into(), Type::Int),
|
||||
("filename".into(), Type::String),
|
||||
|
@ -43,6 +44,7 @@ impl Command for PluginList {
|
|||
description: "List installed plugins.",
|
||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||
"name" => Value::test_string("inc"),
|
||||
"version" => Value::test_string(env!("CARGO_PKG_VERSION")),
|
||||
"is_running" => Value::test_bool(true),
|
||||
"pid" => Value::test_int(106480),
|
||||
"filename" => if cfg!(windows) {
|
||||
|
@ -98,8 +100,15 @@ impl Command for PluginList {
|
|||
.map(|s| Value::string(s.to_string_lossy(), head))
|
||||
.unwrap_or(Value::nothing(head));
|
||||
|
||||
let metadata = plugin.metadata();
|
||||
let version = metadata
|
||||
.and_then(|m| m.version)
|
||||
.map(|s| Value::string(s, head))
|
||||
.unwrap_or(Value::nothing(head));
|
||||
|
||||
let record = record! {
|
||||
"name" => Value::string(plugin.identity().name(), head),
|
||||
"version" => version,
|
||||
"is_running" => Value::bool(plugin.is_running(), head),
|
||||
"pid" => pid,
|
||||
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
||||
|
|
|
@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -20,6 +20,7 @@ pub fn default_shape_color(shape: &str) -> Style {
|
|||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
||||
"shape_glob_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
||||
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
||||
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,21 +13,21 @@ version = "0.94.3"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.3" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.95.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.95.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nuon = { path = "../nuon", version = "0.94.3" }
|
||||
nuon = { path = "../nuon", version = "0.95.1" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
@ -134,11 +134,10 @@ workspace = true
|
|||
plugin = ["nu-parser/plugin"]
|
||||
sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
which-support = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
|
||||
dirs-next = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -146,4 +145,4 @@ quickcheck = { workspace = true }
|
|||
quickcheck_macros = { workspace = true }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tempfile = { workspace = true }
|
|
@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||
let range = &args.indexes;
|
||||
match input {
|
||||
Value::Binary { val, .. } => {
|
||||
use std::cmp::{self, Ordering};
|
||||
let len = val.len() as isize;
|
||||
|
||||
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
||||
let end = if range.1 < 0 { range.1 + len } else { range.1 };
|
||||
|
||||
let end = if range.1 < 0 {
|
||||
cmp::max(range.1 + len, 0)
|
||||
} else {
|
||||
range.1
|
||||
};
|
||||
|
||||
if start < len && end >= 0 {
|
||||
match start.cmp(&end) {
|
||||
Ordering::Equal => Value::binary(vec![], head),
|
||||
Ordering::Greater => Value::error(
|
||||
ShellError::TypeMismatch {
|
||||
err_message: "End must be greater than or equal to Start".to_string(),
|
||||
span: head,
|
||||
},
|
||||
head,
|
||||
),
|
||||
Ordering::Less => Value::binary(
|
||||
if end == isize::MAX {
|
||||
val.iter()
|
||||
.skip(start as usize)
|
||||
.copied()
|
||||
.collect::<Vec<u8>>()
|
||||
} else {
|
||||
val.iter()
|
||||
.skip(start as usize)
|
||||
.take((end - start) as usize)
|
||||
.copied()
|
||||
.collect()
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
if start > end {
|
||||
Value::binary(vec![], head)
|
||||
} else {
|
||||
let val_iter = val.iter().skip(start as usize);
|
||||
Value::binary(
|
||||
if end == isize::MAX {
|
||||
val_iter.copied().collect::<Vec<u8>>()
|
||||
} else {
|
||||
val_iter.take((end - start + 1) as usize).copied().collect()
|
||||
},
|
||||
head,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Value::Error { .. } => input.clone(),
|
||||
|
||||
other => Value::error(
|
||||
|
|
|
@ -194,7 +194,7 @@ fn run_histogram(
|
|||
if inputs.is_empty() {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: col_name.clone(),
|
||||
span: head_span,
|
||||
span: Some(head_span),
|
||||
src_span: list_span,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ fn record_to_path_member(
|
|||
let Some(value) = record.get("value") else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: "value".into(),
|
||||
span: val_span,
|
||||
span: Some(val_span),
|
||||
src_span: span,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||
Value::Filesize { .. } => input.clone(),
|
||||
Value::Int { val, .. } => Value::filesize(*val, value_span),
|
||||
Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
|
||||
Value::String { val, .. } => match int_from_string(val, value_span) {
|
||||
Value::String { val, .. } => match i64_from_string(val, value_span) {
|
||||
Ok(val) => Value::filesize(val, value_span),
|
||||
Err(error) => Value::error(error, value_span),
|
||||
},
|
||||
|
@ -139,7 +139,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
fn i64_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
// Get the Locale so we know what the thousands separator is
|
||||
let locale = get_system_locale();
|
||||
|
||||
|
@ -151,24 +151,35 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||
// Hadle negative file size
|
||||
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
|
||||
match stripped_negative_string.parse::<bytesize::ByteSize>() {
|
||||
Ok(n) => Ok(-(n.as_u64() as i64)),
|
||||
Ok(n) => i64_from_byte_size(n, true, span),
|
||||
Err(_) => Err(string_convert_error(span)),
|
||||
}
|
||||
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
|
||||
match stripped_positive_string.parse::<bytesize::ByteSize>() {
|
||||
Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => {
|
||||
Ok(n.as_u64() as i64)
|
||||
i64_from_byte_size(n, false, span)
|
||||
}
|
||||
_ => Err(string_convert_error(span)),
|
||||
}
|
||||
} else {
|
||||
match clean_string.parse::<bytesize::ByteSize>() {
|
||||
Ok(n) => Ok(n.as_u64() as i64),
|
||||
Ok(n) => i64_from_byte_size(n, false, span),
|
||||
Err(_) => Err(string_convert_error(span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn i64_from_byte_size(
|
||||
byte_size: bytesize::ByteSize,
|
||||
is_negative: bool,
|
||||
span: Span,
|
||||
) -> Result<i64, ShellError> {
|
||||
match i64::try_from(byte_size.as_u64()) {
|
||||
Ok(n) => Ok(if is_negative { -n } else { n }),
|
||||
Err(_) => Err(string_convert_error(span)),
|
||||
}
|
||||
}
|
||||
|
||||
fn string_convert_error(span: Span) -> ShellError {
|
||||
ShellError::CantConvert {
|
||||
to_type: "filesize".into(),
|
||||
|
|
|
@ -127,7 +127,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
SysTemp,
|
||||
SysUsers,
|
||||
UName,
|
||||
|
||||
Which,
|
||||
};
|
||||
|
||||
// Help
|
||||
|
@ -173,9 +173,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
))]
|
||||
bind_command! { Ps };
|
||||
|
||||
#[cfg(feature = "which-support")]
|
||||
bind_command! { Which };
|
||||
|
||||
// Strings
|
||||
bind_command! {
|
||||
Char,
|
||||
|
|
10
crates/nu-command/src/env/export_env.rs
vendored
10
crates/nu-command/src/env/export_env.rs
vendored
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportEnv;
|
||||
|
@ -23,6 +24,15 @@ impl Command for ExportEnv {
|
|||
"Run a block and preserve its environment in a current scope."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
10
crates/nu-command/src/env/source_env.rs
vendored
10
crates/nu-command/src/env/source_env.rs
vendored
|
@ -2,6 +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 std::path::PathBuf;
|
||||
|
||||
/// Source a file for environment variables.
|
||||
|
@ -28,6 +29,15 @@ impl Command for SourceEnv {
|
|||
"Source the environment from a source file into the current environment."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -130,7 +130,7 @@ pub fn split(
|
|||
Some(group_key) => Ok(group_key.coerce_string()?),
|
||||
None => Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.item.to_string(),
|
||||
span: column_name.span,
|
||||
span: Some(column_name.span),
|
||||
src_span: row.span(),
|
||||
}),
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr
|
|||
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: nonexistent,
|
||||
span,
|
||||
span: Some(span),
|
||||
src_span: val_span,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, ClosureEval};
|
||||
use nu_protocol::engine::Closure;
|
||||
use nu_protocol::engine::{Closure, CommandType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Where;
|
||||
|
@ -19,6 +19,10 @@ tables, known as "row conditions". On the other hand, reading the condition from
|
|||
not supported."#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("where")
|
||||
.input_output_types(vec![
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ast::{self, Expr, Expression};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
@ -14,6 +15,7 @@ struct Arguments {
|
|||
month_names: bool,
|
||||
full_year: Option<Spanned<i64>>,
|
||||
week_start: Option<Spanned<String>>,
|
||||
as_table: bool,
|
||||
}
|
||||
|
||||
impl Command for Cal {
|
||||
|
@ -26,6 +28,7 @@ impl Command for Cal {
|
|||
.switch("year", "Display the year column", Some('y'))
|
||||
.switch("quarter", "Display the quarter column", Some('q'))
|
||||
.switch("month", "Display the month column", Some('m'))
|
||||
.switch("as-table", "output as a table", Some('t'))
|
||||
.named(
|
||||
"full-year",
|
||||
SyntaxShape::Int,
|
||||
|
@ -43,7 +46,10 @@ impl Command for Cal {
|
|||
"Display the month names instead of integers",
|
||||
None,
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::table()),
|
||||
(Type::Nothing, Type::String),
|
||||
])
|
||||
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
||||
.category(Category::Generators)
|
||||
}
|
||||
|
@ -75,10 +81,15 @@ impl Command for Cal {
|
|||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "This month's calendar with the week starting on monday",
|
||||
description: "This month's calendar with the week starting on Monday",
|
||||
example: "cal --week-start mo",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "How many 'Friday the Thirteenths' occurred in 2015?",
|
||||
example: "cal --as-table --full-year 2015 | where fr == 13 | length",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +112,7 @@ pub fn cal(
|
|||
quarter: call.has_flag(engine_state, stack, "quarter")?,
|
||||
full_year: call.get_flag(engine_state, stack, "full-year")?,
|
||||
week_start: call.get_flag(engine_state, stack, "week-start")?,
|
||||
as_table: call.has_flag(engine_state, stack, "as-table")?,
|
||||
};
|
||||
|
||||
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
||||
|
@ -131,7 +143,32 @@ pub fn cal(
|
|||
style_computer,
|
||||
)?;
|
||||
|
||||
Ok(Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data())
|
||||
let mut table_no_index = ast::Call::new(Span::unknown());
|
||||
table_no_index.add_named((
|
||||
Spanned {
|
||||
item: "index".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None,
|
||||
Some(Expression::new_unknown(
|
||||
Expr::Bool(false),
|
||||
Span::unknown(),
|
||||
Type::Bool,
|
||||
)),
|
||||
));
|
||||
|
||||
let cal_table_output =
|
||||
Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data();
|
||||
if !arguments.as_table {
|
||||
crate::Table.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&(&table_no_index).into(),
|
||||
cal_table_output,
|
||||
)
|
||||
} else {
|
||||
Ok(cal_table_output)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_invalid_year_shell_error(head: Span) -> ShellError {
|
||||
|
|
|
@ -12,13 +12,7 @@ impl Command for Generate {
|
|||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("generate")
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::List(Box::new(Type::Any))),
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
),
|
||||
])
|
||||
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
|
||||
.required("initial", SyntaxShape::Any, "Initial value.")
|
||||
.required(
|
||||
"closure",
|
||||
|
@ -63,23 +57,10 @@ used as the next argument to the closure, otherwise generation stops.
|
|||
)),
|
||||
},
|
||||
Example {
|
||||
example: "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10",
|
||||
description: "Generate a stream of fibonacci numbers",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_int(0),
|
||||
Value::test_int(1),
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
Value::test_int(3),
|
||||
Value::test_int(5),
|
||||
Value::test_int(8),
|
||||
Value::test_int(13),
|
||||
Value::test_int(21),
|
||||
Value::test_int(34),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
example:
|
||||
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
|
||||
description: "Generate a continuous stream of Fibonacci numbers",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use super::PathSubcommandArguments;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pwd: PathBuf,
|
||||
|
@ -36,7 +39,7 @@ impl Command for SubCommand {
|
|||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This checks the file system to confirm the path's object type.
|
||||
If nothing is found, an empty string will be returned."#
|
||||
If the path does not exist, null will be returned."#
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
|
@ -104,9 +107,11 @@ If nothing is found, an empty string will be returned."#
|
|||
|
||||
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
let path = nu_path::expand_path_with(path, &args.pwd, true);
|
||||
let meta = path.symlink_metadata();
|
||||
let ty = meta.as_ref().map(get_file_type).unwrap_or("");
|
||||
Value::string(ty, span)
|
||||
match path.symlink_metadata() {
|
||||
Ok(metadata) => Value::string(get_file_type(&metadata), span),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span),
|
||||
Err(err) => Value::error(err.into_spanned(span).into(), span),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
|
|
|
@ -81,7 +81,7 @@ pub fn sort(
|
|||
if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: nonexistent,
|
||||
span,
|
||||
span: Some(span),
|
||||
src_span: val_span,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ struct Arguments {
|
|||
substring: String,
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
case_insensitive: bool,
|
||||
not_contain: bool,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
|
@ -40,7 +39,6 @@ impl Command for SubCommand {
|
|||
"For a data structure input, check strings at the given cell paths, and replace with result.",
|
||||
)
|
||||
.switch("ignore-case", "search is case insensitive", Some('i'))
|
||||
.switch("not", "DEPRECATED OPTION: does not contain", Some('n'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
|
@ -63,27 +61,12 @@ impl Command for SubCommand {
|
|||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if call.has_flag(engine_state, stack, "not")? {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Deprecated option".into(),
|
||||
msg: "`str contains --not {string}` is deprecated and will be removed in 0.95."
|
||||
.into(),
|
||||
span: Some(call.head),
|
||||
help: Some("Please use the `not` operator instead.".into()),
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
let args = Arguments {
|
||||
substring: call.req::<String>(engine_state, stack, 0)?,
|
||||
cell_paths,
|
||||
case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?,
|
||||
not_contain: call.has_flag(engine_state, stack, "not")?,
|
||||
};
|
||||
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
@ -114,7 +97,6 @@ impl Command for SubCommand {
|
|||
substring: call.req_const::<String>(working_set, 0)?,
|
||||
cell_paths,
|
||||
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
|
||||
not_contain: call.has_flag_const(working_set, "not")?,
|
||||
};
|
||||
operate(
|
||||
action,
|
||||
|
@ -183,7 +165,6 @@ fn action(
|
|||
input: &Value,
|
||||
Arguments {
|
||||
case_insensitive,
|
||||
not_contain,
|
||||
substring,
|
||||
..
|
||||
}: &Arguments,
|
||||
|
@ -191,23 +172,11 @@ fn action(
|
|||
) -> Value {
|
||||
match input {
|
||||
Value::String { val, .. } => Value::bool(
|
||||
match case_insensitive {
|
||||
true => {
|
||||
if *not_contain {
|
||||
!val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
} else {
|
||||
val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
}
|
||||
}
|
||||
false => {
|
||||
if *not_contain {
|
||||
!val.contains(substring)
|
||||
} else {
|
||||
val.contains(substring)
|
||||
}
|
||||
}
|
||||
if *case_insensitive {
|
||||
val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
} else {
|
||||
val.contains(substring)
|
||||
},
|
||||
head,
|
||||
),
|
||||
|
|
|
@ -5,7 +5,6 @@ use nu_cmd_base::{
|
|||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{engine::StateWorkingSet, Range};
|
||||
use std::cmp::Ordering;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -151,6 +150,11 @@ impl Command for SubCommand {
|
|||
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
||||
result: Some(Value::test_string("ふが")),
|
||||
},
|
||||
Example {
|
||||
description: "sub string by negative index",
|
||||
example: " 'good nushell' | str substring 5..-2",
|
||||
result: Some(Value::test_string("nushel")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -167,56 +171,46 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||
options.0
|
||||
};
|
||||
let end: isize = if options.1 < 0 {
|
||||
std::cmp::max(len + options.1, 0)
|
||||
options.1 + len
|
||||
} else {
|
||||
options.1
|
||||
};
|
||||
|
||||
if start < len && end >= 0 {
|
||||
match start.cmp(&end) {
|
||||
Ordering::Equal => Value::string("", head),
|
||||
Ordering::Greater => Value::error(
|
||||
ShellError::TypeMismatch {
|
||||
err_message: "End must be greater than or equal to Start".to_string(),
|
||||
span: head,
|
||||
},
|
||||
head,
|
||||
),
|
||||
Ordering::Less => Value::string(
|
||||
{
|
||||
if end == isize::MAX {
|
||||
if args.graphemes {
|
||||
s.graphemes(true)
|
||||
.skip(start as usize)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
} else {
|
||||
String::from_utf8_lossy(
|
||||
&s.bytes().skip(start as usize).collect::<Vec<_>>(),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
} else if args.graphemes {
|
||||
if start > end {
|
||||
Value::string("", head)
|
||||
} else {
|
||||
Value::string(
|
||||
{
|
||||
if end == isize::MAX {
|
||||
if args.graphemes {
|
||||
s.graphemes(true)
|
||||
.skip(start as usize)
|
||||
.take((end - start) as usize)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
} else {
|
||||
String::from_utf8_lossy(
|
||||
&s.bytes()
|
||||
.skip(start as usize)
|
||||
.take((end - start) as usize)
|
||||
.collect::<Vec<_>>(),
|
||||
&s.bytes().skip(start as usize).collect::<Vec<_>>(),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Value::string("", head)
|
||||
} else if args.graphemes {
|
||||
s.graphemes(true)
|
||||
.skip(start as usize)
|
||||
.take((end - start + 1) as usize)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
} else {
|
||||
String::from_utf8_lossy(
|
||||
&s.bytes()
|
||||
.skip(start as usize)
|
||||
.take((end - start + 1) as usize)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
},
|
||||
head,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
|
@ -243,6 +237,7 @@ mod tests {
|
|||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Expectation<'a> {
|
||||
options: (isize, isize),
|
||||
expected: &'a str,
|
||||
|
@ -266,18 +261,19 @@ mod tests {
|
|||
let word = Value::test_string("andres");
|
||||
|
||||
let cases = vec![
|
||||
expectation("a", (0, 1)),
|
||||
expectation("an", (0, 2)),
|
||||
expectation("and", (0, 3)),
|
||||
expectation("andr", (0, 4)),
|
||||
expectation("andre", (0, 5)),
|
||||
expectation("a", (0, 0)),
|
||||
expectation("an", (0, 1)),
|
||||
expectation("and", (0, 2)),
|
||||
expectation("andr", (0, 3)),
|
||||
expectation("andre", (0, 4)),
|
||||
expectation("andres", (0, 5)),
|
||||
expectation("andres", (0, 6)),
|
||||
expectation("", (0, -6)),
|
||||
expectation("a", (0, -5)),
|
||||
expectation("an", (0, -4)),
|
||||
expectation("and", (0, -3)),
|
||||
expectation("andr", (0, -2)),
|
||||
expectation("andre", (0, -1)),
|
||||
expectation("a", (0, -6)),
|
||||
expectation("an", (0, -5)),
|
||||
expectation("and", (0, -4)),
|
||||
expectation("andr", (0, -3)),
|
||||
expectation("andre", (0, -2)),
|
||||
expectation("andres", (0, -1)),
|
||||
// str substring [ -4 , _ ]
|
||||
// str substring -4 ,
|
||||
expectation("dres", (-4, isize::MAX)),
|
||||
|
@ -292,6 +288,7 @@ mod tests {
|
|||
];
|
||||
|
||||
for expectation in &cases {
|
||||
println!("{:?}", expectation);
|
||||
let expected = expectation.expected;
|
||||
let actual = action(
|
||||
&word,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression};
|
||||
use nu_path::{dots::expand_ndots, expand_tilde};
|
||||
use nu_protocol::{
|
||||
ast::{self, Expr, Expression},
|
||||
ast::{self, Expression},
|
||||
did_you_mean,
|
||||
process::ChildProcess,
|
||||
ByteStream, NuGlob, OutDest,
|
||||
|
@ -11,6 +12,7 @@ use nu_utils::IgnoreCaseExt;
|
|||
use pathdiff::diff_paths;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
|
@ -33,8 +35,16 @@ impl Command for External {
|
|||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("command", SyntaxShape::String, "External command to run.")
|
||||
.rest("args", SyntaxShape::Any, "Arguments for external command.")
|
||||
.required(
|
||||
"command",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
|
||||
"External command to run.",
|
||||
)
|
||||
.rest(
|
||||
"args",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]),
|
||||
"Arguments for external command.",
|
||||
)
|
||||
.category(Category::System)
|
||||
}
|
||||
|
||||
|
@ -50,42 +60,33 @@ impl Command for External {
|
|||
let call = call.assert_ast_call()?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
// Evaluate the command name in the same way the arguments are evaluated. Since this isn't
|
||||
// a spread, it should return a one-element vec.
|
||||
let name_expr = call
|
||||
.positional_nth(0)
|
||||
.ok_or_else(|| ShellError::MissingParameter {
|
||||
param_name: "command".into(),
|
||||
span: call.head,
|
||||
})?;
|
||||
let name = eval_argument(engine_state, stack, name_expr, false)?
|
||||
.pop()
|
||||
.expect("eval_argument returned zero-element vec")
|
||||
.into_spanned(name_expr.span);
|
||||
let name: Value = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let name_str: Cow<str> = match &name {
|
||||
Value::Glob { val, .. } => Cow::Borrowed(val),
|
||||
Value::String { val, .. } => Cow::Borrowed(val),
|
||||
_ => Cow::Owned(name.clone().coerce_into_string()?),
|
||||
};
|
||||
|
||||
let expanded_name = match &name {
|
||||
// Expand tilde and ndots on the name if it's a bare string / glob (#13000)
|
||||
Value::Glob { no_expand, .. } if !*no_expand => {
|
||||
expand_ndots_safe(expand_tilde(&*name_str))
|
||||
}
|
||||
_ => Path::new(&*name_str).to_owned(),
|
||||
};
|
||||
|
||||
// Find the absolute path to the executable. On Windows, set the
|
||||
// executable to "cmd.exe" if it's is a CMD internal command. If the
|
||||
// command is not found, display a helpful error message.
|
||||
let executable = if cfg!(windows) && is_cmd_internal_command(&name.item) {
|
||||
let executable = if cfg!(windows) && is_cmd_internal_command(&name_str) {
|
||||
PathBuf::from("cmd.exe")
|
||||
} else {
|
||||
// Expand tilde on the name if it's a bare string (#13000)
|
||||
let expanded_name = if is_bare_string(name_expr) {
|
||||
expand_tilde(&name.item)
|
||||
} else {
|
||||
name.item.clone()
|
||||
};
|
||||
|
||||
// Determine the PATH to be used and then use `which` to find it - though this has no
|
||||
// effect if it's an absolute path already
|
||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||
let Some(executable) = which(&expanded_name, &paths, &cwd) else {
|
||||
return Err(command_not_found(
|
||||
&name.item,
|
||||
call.head,
|
||||
engine_state,
|
||||
stack,
|
||||
));
|
||||
let Some(executable) = which(expanded_name, &paths, &cwd) else {
|
||||
return Err(command_not_found(&name_str, call.head, engine_state, stack));
|
||||
};
|
||||
executable
|
||||
};
|
||||
|
@ -104,15 +105,15 @@ impl Command for External {
|
|||
// Configure args.
|
||||
let args = eval_arguments_from_call(engine_state, stack, call)?;
|
||||
#[cfg(windows)]
|
||||
if is_cmd_internal_command(&name.item) {
|
||||
if is_cmd_internal_command(&name_str) {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
// The /D flag disables execution of AutoRun commands from registry.
|
||||
// The /C flag followed by a command name instructs CMD to execute
|
||||
// that command and quit.
|
||||
command.args(["/D", "/C", &name.item]);
|
||||
command.args(["/D", "/C", &name_str]);
|
||||
for arg in &args {
|
||||
command.raw_arg(escape_cmd_argument(arg)?.as_ref());
|
||||
command.raw_arg(escape_cmd_argument(arg)?);
|
||||
}
|
||||
} else {
|
||||
command.args(args.into_iter().map(|s| s.item));
|
||||
|
@ -220,76 +221,54 @@ impl Command for External {
|
|||
}
|
||||
}
|
||||
|
||||
/// Removes surrounding quotes from a string. Doesn't remove quotes from raw
|
||||
/// strings. Returns the original string if it doesn't have matching quotes.
|
||||
fn remove_quotes(s: &str) -> Cow<'_, str> {
|
||||
let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"');
|
||||
let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\'');
|
||||
let quoted_by_backticks = s.len() >= 2 && s.starts_with('`') && s.ends_with('`');
|
||||
if quoted_by_double_quotes {
|
||||
Cow::Owned(s[1..s.len() - 1].to_string().replace(r#"\""#, "\""))
|
||||
} else if quoted_by_single_quotes || quoted_by_backticks {
|
||||
Cow::Borrowed(&s[1..s.len() - 1])
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate all arguments from a call, performing expansions when necessary.
|
||||
pub fn eval_arguments_from_call(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &ast::Call,
|
||||
) -> Result<Vec<Spanned<String>>, ShellError> {
|
||||
) -> Result<Vec<Spanned<OsString>>, ShellError> {
|
||||
let ctrlc = &engine_state.ctrlc;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let mut args: Vec<Spanned<String>> = vec![];
|
||||
let mut args: Vec<Spanned<OsString>> = vec![];
|
||||
for (expr, spread) in call.rest_iter(1) {
|
||||
if is_bare_string(expr) {
|
||||
// If `expr` is a bare string, perform tilde-expansion,
|
||||
// glob-expansion, and inner-quotes-removal, in that order.
|
||||
for arg in eval_argument(engine_state, stack, expr, spread)? {
|
||||
let tilde_expanded = expand_tilde(&arg);
|
||||
for glob_expanded in expand_glob(&tilde_expanded, &cwd, expr.span, ctrlc)? {
|
||||
let inner_quotes_removed = remove_inner_quotes(&glob_expanded);
|
||||
args.push(inner_quotes_removed.into_owned().into_spanned(expr.span));
|
||||
for arg in eval_argument(engine_state, stack, expr, spread)? {
|
||||
match arg {
|
||||
// Expand globs passed to run-external
|
||||
Value::Glob { val, no_expand, .. } if !no_expand => args.extend(
|
||||
expand_glob(&val, &cwd, expr.span, ctrlc)?
|
||||
.into_iter()
|
||||
.map(|s| s.into_spanned(expr.span)),
|
||||
),
|
||||
other => {
|
||||
args.push(OsString::from(coerce_into_string(other)?).into_spanned(expr.span))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for arg in eval_argument(engine_state, stack, expr, spread)? {
|
||||
args.push(arg.into_spanned(expr.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
/// Evaluates an expression, coercing the values to strings.
|
||||
///
|
||||
/// Note: The parser currently has a special hack that retains surrounding
|
||||
/// quotes for string literals in `Expression`, so that we can decide whether
|
||||
/// the expression is considered a bare string. The hack doesn't affect string
|
||||
/// literals within lists or records. This function will remove the quotes
|
||||
/// before evaluating the expression.
|
||||
/// Custom `coerce_into_string()`, including globs, since those are often args to `run-external`
|
||||
/// as well
|
||||
fn coerce_into_string(val: Value) -> Result<String, ShellError> {
|
||||
match val {
|
||||
Value::Glob { val, .. } => Ok(val),
|
||||
_ => val.coerce_into_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate an argument, returning more than one value if it was a list to be spread.
|
||||
fn eval_argument(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
expr: &Expression,
|
||||
spread: bool,
|
||||
) -> Result<Vec<String>, ShellError> {
|
||||
// Remove quotes from string literals.
|
||||
let mut expr = expr.clone();
|
||||
if let Expr::String(s) = &expr.expr {
|
||||
expr.expr = Expr::String(remove_quotes(s).into());
|
||||
}
|
||||
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let eval = get_eval_expression(engine_state);
|
||||
match eval(engine_state, stack, &expr)? {
|
||||
match eval(engine_state, stack, expr)? {
|
||||
Value::List { vals, .. } => {
|
||||
if spread {
|
||||
vals.into_iter()
|
||||
.map(|val| val.coerce_into_string())
|
||||
.collect()
|
||||
Ok(vals)
|
||||
} else {
|
||||
Err(ShellError::CannotPassListToExternal {
|
||||
arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(),
|
||||
|
@ -301,31 +280,12 @@ fn eval_argument(
|
|||
if spread {
|
||||
Err(ShellError::CannotSpreadAsList { span: expr.span })
|
||||
} else {
|
||||
Ok(vec![value.coerce_into_string()?])
|
||||
Ok(vec![value])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether an expression is considered a bare string.
|
||||
///
|
||||
/// Bare strings are defined as string literals that are either unquoted or
|
||||
/// quoted by backticks. Raw strings or string interpolations don't count.
|
||||
fn is_bare_string(expr: &Expression) -> bool {
|
||||
let Expr::String(s) = &expr.expr else {
|
||||
return false;
|
||||
};
|
||||
let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"');
|
||||
let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\'');
|
||||
!quoted_by_double_quotes && !quoted_by_single_quotes
|
||||
}
|
||||
|
||||
/// Performs tilde expansion on `arg`. Returns the original string if `arg`
|
||||
/// doesn't start with tilde.
|
||||
fn expand_tilde(arg: &str) -> String {
|
||||
nu_path::expand_tilde(arg).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
/// Performs glob expansion on `arg`. If the expansion found no matches or the pattern
|
||||
/// is not a valid glob, then this returns the original string as the expansion result.
|
||||
///
|
||||
|
@ -336,19 +296,21 @@ fn expand_glob(
|
|||
cwd: &Path,
|
||||
span: Span,
|
||||
interrupt: &Option<Arc<AtomicBool>>,
|
||||
) -> Result<Vec<String>, ShellError> {
|
||||
) -> Result<Vec<OsString>, ShellError> {
|
||||
const GLOB_CHARS: &[char] = &['*', '?', '['];
|
||||
|
||||
// Don't expand something that doesn't include the GLOB_CHARS
|
||||
// For an argument that doesn't include the GLOB_CHARS, just do the `expand_tilde`
|
||||
// and `expand_ndots` expansion
|
||||
if !arg.contains(GLOB_CHARS) {
|
||||
return Ok(vec![arg.into()]);
|
||||
let path = expand_ndots_safe(expand_tilde(arg));
|
||||
return Ok(vec![path.into()]);
|
||||
}
|
||||
|
||||
// We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct
|
||||
// dir
|
||||
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
|
||||
if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
|
||||
let mut result = vec![];
|
||||
let mut result: Vec<OsString> = vec![];
|
||||
|
||||
for m in matches {
|
||||
if nu_utils::ctrl_c::was_pressed(interrupt) {
|
||||
|
@ -356,7 +318,7 @@ fn expand_glob(
|
|||
}
|
||||
if let Ok(arg) = m {
|
||||
let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd);
|
||||
result.push(arg.to_string_lossy().to_string());
|
||||
result.push(arg.into());
|
||||
} else {
|
||||
result.push(arg.into());
|
||||
}
|
||||
|
@ -395,23 +357,6 @@ fn resolve_globbed_path_to_cwd_relative(
|
|||
}
|
||||
}
|
||||
|
||||
/// Transforms `--option="value"` into `--option=value`. `value` can be quoted
|
||||
/// with double quotes, single quotes, or backticks. Only removes the outermost
|
||||
/// pair of quotes after the equal sign.
|
||||
fn remove_inner_quotes(arg: &str) -> Cow<'_, str> {
|
||||
// Split `arg` on the first `=`.
|
||||
let Some((option, value)) = arg.split_once('=') else {
|
||||
return Cow::Borrowed(arg);
|
||||
};
|
||||
// Check that `option` doesn't contain quotes.
|
||||
if option.contains('"') || option.contains('\'') || option.contains('`') {
|
||||
return Cow::Borrowed(arg);
|
||||
}
|
||||
// Remove the outermost pair of quotes from `value`.
|
||||
let value = remove_quotes(value);
|
||||
Cow::Owned(format!("{option}={value}"))
|
||||
}
|
||||
|
||||
/// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is
|
||||
/// first rendered using the `table` command.
|
||||
///
|
||||
|
@ -580,7 +525,7 @@ pub fn command_not_found(
|
|||
/// Note: the `which.rs` crate always uses PATHEXT from the environment. As
|
||||
/// such, changing PATHEXT within Nushell doesn't work without updating the
|
||||
/// actual environment of the Nushell process.
|
||||
pub fn which(name: &str, paths: &str, cwd: &Path) -> Option<PathBuf> {
|
||||
pub fn which(name: impl AsRef<OsStr>, paths: &str, cwd: &Path) -> Option<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
let paths = format!("{};{}", cwd.display(), paths);
|
||||
which::which_in(name, Some(paths), cwd).ok()
|
||||
|
@ -596,17 +541,18 @@ fn is_cmd_internal_command(name: &str) -> bool {
|
|||
}
|
||||
|
||||
/// Returns true if a string contains CMD special characters.
|
||||
#[cfg(windows)]
|
||||
fn has_cmd_special_character(s: &str) -> bool {
|
||||
const SPECIAL_CHARS: &[char] = &['<', '>', '&', '|', '^'];
|
||||
SPECIAL_CHARS.iter().any(|c| s.contains(*c))
|
||||
fn has_cmd_special_character(s: impl AsRef<[u8]>) -> bool {
|
||||
s.as_ref()
|
||||
.iter()
|
||||
.any(|b| matches!(b, b'<' | b'>' | b'&' | b'|' | b'^'))
|
||||
}
|
||||
|
||||
/// Escape an argument for CMD internal commands. The result can be safely passed to `raw_arg()`.
|
||||
#[cfg(windows)]
|
||||
fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError> {
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn escape_cmd_argument(arg: &Spanned<OsString>) -> Result<Cow<'_, OsStr>, ShellError> {
|
||||
let Spanned { item: arg, span } = arg;
|
||||
if arg.contains(['\r', '\n', '%']) {
|
||||
let bytes = arg.as_encoded_bytes();
|
||||
if bytes.iter().any(|b| matches!(b, b'\r' | b'\n' | b'%')) {
|
||||
// \r and \n trunacte the rest of the arguments and % can expand environment variables
|
||||
Err(ShellError::ExternalCommand {
|
||||
label:
|
||||
|
@ -615,12 +561,12 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
|
|||
help: "some characters currently cannot be securely escaped".into(),
|
||||
span: *span,
|
||||
})
|
||||
} else if arg.contains('"') {
|
||||
} else if bytes.contains(&b'"') {
|
||||
// If `arg` is already quoted by double quotes, confirm there's no
|
||||
// embedded double quotes, then leave it as is.
|
||||
if arg.chars().filter(|c| *c == '"').count() == 2
|
||||
&& arg.starts_with('"')
|
||||
&& arg.ends_with('"')
|
||||
if bytes.iter().filter(|b| **b == b'"').count() == 2
|
||||
&& bytes.starts_with(b"\"")
|
||||
&& bytes.ends_with(b"\"")
|
||||
{
|
||||
Ok(Cow::Borrowed(arg))
|
||||
} else {
|
||||
|
@ -631,76 +577,39 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
|
|||
span: *span,
|
||||
})
|
||||
}
|
||||
} else if arg.contains(' ') || has_cmd_special_character(arg) {
|
||||
} else if bytes.contains(&b' ') || has_cmd_special_character(bytes) {
|
||||
// If `arg` contains space or special characters, quote the entire argument by double quotes.
|
||||
Ok(Cow::Owned(format!("\"{arg}\"")))
|
||||
let mut new_str = OsString::new();
|
||||
new_str.push("\"");
|
||||
new_str.push(arg);
|
||||
new_str.push("\"");
|
||||
Ok(Cow::Owned(new_str))
|
||||
} else {
|
||||
// FIXME?: what if `arg.is_empty()`?
|
||||
Ok(Cow::Borrowed(arg))
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand ndots, but only if it looks like it probably contains them, because there is some lossy
|
||||
/// path normalization that happens.
|
||||
fn expand_ndots_safe(path: impl AsRef<Path>) -> PathBuf {
|
||||
let string = path.as_ref().to_string_lossy();
|
||||
|
||||
// Use ndots if it contains at least `...`, since that's the minimum trigger point, and don't
|
||||
// use it if it contains ://, because that looks like a URL scheme and the path normalization
|
||||
// will mess with that.
|
||||
if string.contains("...") && !string.contains("://") {
|
||||
expand_ndots(path)
|
||||
} else {
|
||||
path.as_ref().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_protocol::ast::ListItem;
|
||||
use nu_test_support::{fs::Stub, playground::Playground};
|
||||
|
||||
#[test]
|
||||
fn test_remove_quotes() {
|
||||
assert_eq!(remove_quotes(r#""#), r#""#);
|
||||
assert_eq!(remove_quotes(r#"'"#), r#"'"#);
|
||||
assert_eq!(remove_quotes(r#"''"#), r#""#);
|
||||
assert_eq!(remove_quotes(r#""foo""#), r#"foo"#);
|
||||
assert_eq!(remove_quotes(r#"`foo '"' bar`"#), r#"foo '"' bar"#);
|
||||
assert_eq!(remove_quotes(r#"'foo' bar"#), r#"'foo' bar"#);
|
||||
assert_eq!(remove_quotes(r#"r#'foo'#"#), r#"r#'foo'#"#);
|
||||
assert_eq!(remove_quotes(r#""foo\" bar""#), r#"foo" bar"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_argument() {
|
||||
fn expression(expr: Expr) -> Expression {
|
||||
Expression::new_unknown(expr, Span::unknown(), Type::Any)
|
||||
}
|
||||
|
||||
fn eval(expr: Expr, spread: bool) -> Result<Vec<String>, ShellError> {
|
||||
let engine_state = EngineState::new();
|
||||
let mut stack = Stack::new();
|
||||
eval_argument(&engine_state, &mut stack, &expression(expr), spread)
|
||||
}
|
||||
|
||||
let actual = eval(Expr::String("".into()), false).unwrap();
|
||||
let expected = &[""];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = eval(Expr::String("'foo'".into()), false).unwrap();
|
||||
let expected = &["foo"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = eval(Expr::RawString("'foo'".into()), false).unwrap();
|
||||
let expected = &["'foo'"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = eval(Expr::List(vec![]), true).unwrap();
|
||||
let expected: &[&str] = &[];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = eval(
|
||||
Expr::List(vec![
|
||||
ListItem::Item(expression(Expr::String("'foo'".into()))),
|
||||
ListItem::Item(expression(Expr::String("bar".into()))),
|
||||
]),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let expected = &["'foo'", "bar"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
eval(Expr::String("".into()), true).unwrap_err();
|
||||
eval(Expr::List(vec![]), false).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_glob() {
|
||||
Playground::setup("test_expand_glob", |dirs, play| {
|
||||
|
@ -730,40 +639,14 @@ mod test {
|
|||
let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap();
|
||||
let expected = &["[*.txt"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &None).unwrap();
|
||||
let home = dirs_next::home_dir().expect("failed to get home dir");
|
||||
let expected: Vec<OsString> = vec![home.join("foo.txt").into()];
|
||||
assert_eq!(actual, expected);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_inner_quotes() {
|
||||
let actual = remove_inner_quotes(r#"--option=value"#);
|
||||
let expected = r#"--option=value"#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"--option="value""#);
|
||||
let expected = r#"--option=value"#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"--option='value'"#);
|
||||
let expected = r#"--option=value"#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"--option "value""#);
|
||||
let expected = r#"--option "value""#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"-option="value""#);
|
||||
let expected = r#"-option=value"#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"option="value""#);
|
||||
let expected = r#"option=value"#;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = remove_inner_quotes(r#"option="v\"value""#);
|
||||
let expected = r#"option=v"value"#;
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_pipeline_data() {
|
||||
let engine_state = EngineState::new();
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysCpu;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysCpu {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::cpu(call.head).into_pipeline_data())
|
||||
Ok(cpu(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -37,3 +39,42 @@ impl Command for SysCpu {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn cpu(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
|
||||
// We must refresh the CPU twice a while apart to get valid usage data.
|
||||
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
|
||||
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
|
||||
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
|
||||
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
|
||||
|
||||
let cpus = sys
|
||||
.cpus()
|
||||
.iter()
|
||||
.map(|cpu| {
|
||||
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
|
||||
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
|
||||
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
|
||||
|
||||
let load_avg = System::load_average();
|
||||
let load_avg = format!(
|
||||
"{:.2}, {:.2}, {:.2}",
|
||||
load_avg.one, load_avg.five, load_avg.fifteen
|
||||
);
|
||||
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
|
||||
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
|
||||
"freq" => Value::int(cpu.frequency() as i64, span),
|
||||
"cpu_usage" => Value::float(rounded_usage.into(), span),
|
||||
"load_average" => Value::string(load_avg, span),
|
||||
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(cpus, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Disks;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysDisks;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysDisks {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::disks(call.head).into_pipeline_data())
|
||||
Ok(disks(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -37,3 +39,27 @@ impl Command for SysDisks {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn disks(span: Span) -> Value {
|
||||
let disks = Disks::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
let device = trim_cstyle_null(disk.name().to_string_lossy());
|
||||
let typ = trim_cstyle_null(disk.file_system().to_string_lossy());
|
||||
|
||||
let record = record! {
|
||||
"device" => Value::string(device, span),
|
||||
"type" => Value::string(typ, span),
|
||||
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
|
||||
"total" => Value::filesize(disk.total_space() as i64, span),
|
||||
"free" => Value::filesize(disk.available_space() as i64, span),
|
||||
"removable" => Value::bool(disk.is_removable(), span),
|
||||
"kind" => Value::string(disk.kind().to_string(), span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(disks, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use super::trim_cstyle_null;
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysHost;
|
||||
|
@ -26,8 +29,7 @@ impl Command for SysHost {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let host = super::host(call.head);
|
||||
Ok(Value::record(host, call.head).into_pipeline_data())
|
||||
Ok(host(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -38,3 +40,53 @@ impl Command for SysHost {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn host(span: Span) -> Value {
|
||||
let mut record = Record::new();
|
||||
|
||||
if let Some(name) = System::name() {
|
||||
record.push("name", Value::string(trim_cstyle_null(name), span));
|
||||
}
|
||||
if let Some(version) = System::os_version() {
|
||||
record.push("os_version", Value::string(trim_cstyle_null(version), span));
|
||||
}
|
||||
if let Some(long_version) = System::long_os_version() {
|
||||
record.push(
|
||||
"long_os_version",
|
||||
Value::string(trim_cstyle_null(long_version), span),
|
||||
);
|
||||
}
|
||||
if let Some(version) = System::kernel_version() {
|
||||
record.push(
|
||||
"kernel_version",
|
||||
Value::string(trim_cstyle_null(version), span),
|
||||
);
|
||||
}
|
||||
if let Some(hostname) = System::host_name() {
|
||||
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
|
||||
}
|
||||
|
||||
let uptime = System::uptime()
|
||||
.saturating_mul(1_000_000_000)
|
||||
.try_into()
|
||||
.unwrap_or(i64::MAX);
|
||||
|
||||
record.push("uptime", Value::duration(uptime, span));
|
||||
|
||||
let boot_time = boot_time()
|
||||
.map(|time| Value::date(time, span))
|
||||
.unwrap_or(Value::nothing(span));
|
||||
|
||||
record.push("boot_time", boot_time);
|
||||
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
||||
fn boot_time() -> Option<DateTime<FixedOffset>> {
|
||||
// Broken systems can apparently return really high values.
|
||||
// See: https://github.com/nushell/nushell/issues/10155
|
||||
// First, try to convert u64 to i64, and then try to create a `DateTime`.
|
||||
let secs = System::boot_time().try_into().ok()?;
|
||||
let time = DateTime::from_timestamp(secs, 0)?;
|
||||
Some(time.with_timezone(&Local).fixed_offset())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysMem;
|
||||
|
@ -26,7 +27,7 @@ impl Command for SysMem {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::mem(call.head).into_pipeline_data())
|
||||
Ok(mem(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -37,3 +38,20 @@ impl Command for SysMem {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn mem(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
|
||||
let record = record! {
|
||||
"total" => Value::filesize(sys.total_memory() as i64, span),
|
||||
"free" => Value::filesize(sys.free_memory() as i64, span),
|
||||
"used" => Value::filesize(sys.used_memory() as i64, span),
|
||||
"available" => Value::filesize(sys.available_memory() as i64, span),
|
||||
"swap total" => Value::filesize(sys.total_swap() as i64, span),
|
||||
"swap free" => Value::filesize(sys.free_swap() as i64, span),
|
||||
"swap used" => Value::filesize(sys.used_swap() as i64, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
|
|
@ -16,202 +16,6 @@ pub use sys_::Sys;
|
|||
pub use temp::SysTemp;
|
||||
pub use users::SysUsers;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
use nu_protocol::{record, Record, Span, Value};
|
||||
use sysinfo::{
|
||||
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
|
||||
};
|
||||
|
||||
pub fn trim_cstyle_null(s: impl AsRef<str>) -> String {
|
||||
fn trim_cstyle_null(s: impl AsRef<str>) -> String {
|
||||
s.as_ref().trim_matches('\0').into()
|
||||
}
|
||||
|
||||
pub fn disks(span: Span) -> Value {
|
||||
let disks = Disks::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
let device = trim_cstyle_null(disk.name().to_string_lossy());
|
||||
let typ = trim_cstyle_null(disk.file_system().to_string_lossy());
|
||||
|
||||
let record = record! {
|
||||
"device" => Value::string(device, span),
|
||||
"type" => Value::string(typ, span),
|
||||
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
|
||||
"total" => Value::filesize(disk.total_space() as i64, span),
|
||||
"free" => Value::filesize(disk.available_space() as i64, span),
|
||||
"removable" => Value::bool(disk.is_removable(), span),
|
||||
"kind" => Value::string(disk.kind().to_string(), span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(disks, span)
|
||||
}
|
||||
|
||||
pub fn net(span: Span) -> Value {
|
||||
let networks = Networks::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|(iface, data)| {
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(iface), span),
|
||||
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
||||
"recv" => Value::filesize(data.total_received() as i64, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(networks, span)
|
||||
}
|
||||
|
||||
pub fn cpu(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
|
||||
// We must refresh the CPU twice a while apart to get valid usage data.
|
||||
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
|
||||
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
|
||||
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
|
||||
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
|
||||
|
||||
let cpus = sys
|
||||
.cpus()
|
||||
.iter()
|
||||
.map(|cpu| {
|
||||
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
|
||||
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
|
||||
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
|
||||
|
||||
let load_avg = System::load_average();
|
||||
let load_avg = format!(
|
||||
"{:.2}, {:.2}, {:.2}",
|
||||
load_avg.one, load_avg.five, load_avg.fifteen
|
||||
);
|
||||
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
|
||||
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
|
||||
"freq" => Value::int(cpu.frequency() as i64, span),
|
||||
"cpu_usage" => Value::float(rounded_usage.into(), span),
|
||||
"load_average" => Value::string(load_avg, span),
|
||||
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(cpus, span)
|
||||
}
|
||||
|
||||
pub fn mem(span: Span) -> Value {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
|
||||
let record = record! {
|
||||
"total" => Value::filesize(sys.total_memory() as i64, span),
|
||||
"free" => Value::filesize(sys.free_memory() as i64, span),
|
||||
"used" => Value::filesize(sys.used_memory() as i64, span),
|
||||
"available" => Value::filesize(sys.available_memory() as i64, span),
|
||||
"swap total" => Value::filesize(sys.total_swap() as i64, span),
|
||||
"swap free" => Value::filesize(sys.free_swap() as i64, span),
|
||||
"swap used" => Value::filesize(sys.used_swap() as i64, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
||||
pub fn users(span: Span) -> Value {
|
||||
let users = Users::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|user| {
|
||||
let groups = user
|
||||
.groups()
|
||||
.iter()
|
||||
.map(|group| Value::string(trim_cstyle_null(group.name()), span))
|
||||
.collect();
|
||||
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(user.name()), span),
|
||||
"groups" => Value::list(groups, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(users, span)
|
||||
}
|
||||
|
||||
pub fn host(span: Span) -> Record {
|
||||
let mut record = Record::new();
|
||||
|
||||
if let Some(name) = System::name() {
|
||||
record.push("name", Value::string(trim_cstyle_null(name), span));
|
||||
}
|
||||
if let Some(version) = System::os_version() {
|
||||
record.push("os_version", Value::string(trim_cstyle_null(version), span));
|
||||
}
|
||||
if let Some(long_version) = System::long_os_version() {
|
||||
record.push(
|
||||
"long_os_version",
|
||||
Value::string(trim_cstyle_null(long_version), span),
|
||||
);
|
||||
}
|
||||
if let Some(version) = System::kernel_version() {
|
||||
record.push(
|
||||
"kernel_version",
|
||||
Value::string(trim_cstyle_null(version), span),
|
||||
);
|
||||
}
|
||||
if let Some(hostname) = System::host_name() {
|
||||
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
|
||||
}
|
||||
|
||||
let uptime = System::uptime()
|
||||
.saturating_mul(1_000_000_000)
|
||||
.try_into()
|
||||
.unwrap_or(i64::MAX);
|
||||
|
||||
record.push("uptime", Value::duration(uptime, span));
|
||||
|
||||
let boot_time = boot_time()
|
||||
.map(|time| Value::date(time, span))
|
||||
.unwrap_or(Value::nothing(span));
|
||||
|
||||
record.push("boot_time", boot_time);
|
||||
|
||||
record
|
||||
}
|
||||
|
||||
fn boot_time() -> Option<DateTime<FixedOffset>> {
|
||||
// Broken systems can apparently return really high values.
|
||||
// See: https://github.com/nushell/nushell/issues/10155
|
||||
// First, try to convert u64 to i64, and then try to create a `DateTime`.
|
||||
let secs = System::boot_time().try_into().ok()?;
|
||||
let time = DateTime::from_timestamp(secs, 0)?;
|
||||
Some(time.with_timezone(&Local).fixed_offset())
|
||||
}
|
||||
|
||||
pub fn temp(span: Span) -> Value {
|
||||
let components = Components::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|component| {
|
||||
let mut record = record! {
|
||||
"unit" => Value::string(component.label(), span),
|
||||
"temp" => Value::float(component.temperature().into(), span),
|
||||
"high" => Value::float(component.max().into(), span),
|
||||
};
|
||||
|
||||
if let Some(critical) = component.critical() {
|
||||
record.push("critical", Value::float(critical.into(), span));
|
||||
}
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(components, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Networks;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysNet;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysNet {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::net(call.head).into_pipeline_data())
|
||||
Ok(net(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -37,3 +39,20 @@ impl Command for SysNet {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn net(span: Span) -> Value {
|
||||
let networks = Networks::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|(iface, data)| {
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(iface), span),
|
||||
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
||||
"recv" => Value::filesize(data.total_received() as i64, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(networks, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sys;
|
||||
|
@ -20,41 +20,17 @@ impl Command for Sys {
|
|||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Note that this command may take a noticeable amount of time to run. To reduce the time taken, you can use the various `sys` sub commands to get the subset of information you are interested in."
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Deprecated command".into(),
|
||||
msg: "the `sys` command is deprecated, please use the new subcommands (`sys host`, `sys mem`, etc.)."
|
||||
.into(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
let head = call.head;
|
||||
|
||||
let mut host = super::host(head);
|
||||
host.push("sessions", super::users(head));
|
||||
let record = record! {
|
||||
"host" => Value::record(host, head),
|
||||
"cpu" => super::cpu(head),
|
||||
"disks" => super::disks(head),
|
||||
"mem" => super::mem(head),
|
||||
"temp" => super::temp(head),
|
||||
"net" => super::net(head),
|
||||
};
|
||||
Ok(Value::record(record, head).into_pipeline_data())
|
||||
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Components;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysTemp;
|
||||
|
@ -30,7 +31,7 @@ impl Command for SysTemp {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::temp(call.head).into_pipeline_data())
|
||||
Ok(temp(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -41,3 +42,24 @@ impl Command for SysTemp {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn temp(span: Span) -> Value {
|
||||
let components = Components::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|component| {
|
||||
let mut record = record! {
|
||||
"unit" => Value::string(component.label(), span),
|
||||
"temp" => Value::float(component.temperature().into(), span),
|
||||
"high" => Value::float(component.max().into(), span),
|
||||
};
|
||||
|
||||
if let Some(critical) = component.critical() {
|
||||
record.push("critical", Value::float(critical.into(), span));
|
||||
}
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(components, span)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Users;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysUsers;
|
||||
|
@ -11,7 +13,7 @@ impl Command for SysUsers {
|
|||
fn signature(&self) -> Signature {
|
||||
Signature::build("sys users")
|
||||
.category(Category::System)
|
||||
.input_output_types(vec![(Type::Nothing, Type::record())])
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -25,7 +27,7 @@ impl Command for SysUsers {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::users(call.head).into_pipeline_data())
|
||||
Ok(users(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -36,3 +38,25 @@ impl Command for SysUsers {
|
|||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn users(span: Span) -> Value {
|
||||
let users = Users::new_with_refreshed_list()
|
||||
.iter()
|
||||
.map(|user| {
|
||||
let groups = user
|
||||
.groups()
|
||||
.iter()
|
||||
.map(|group| Value::string(trim_cstyle_null(group.name()), span))
|
||||
.collect();
|
||||
|
||||
let record = record! {
|
||||
"name" => Value::string(trim_cstyle_null(user.name()), span),
|
||||
"groups" => Value::list(groups, span),
|
||||
};
|
||||
|
||||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::list(users, span)
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ fn get_entries_in_nu(
|
|||
all_entries
|
||||
}
|
||||
|
||||
#[cfg(feature = "which-support")]
|
||||
fn get_first_entry_in_path(
|
||||
item: &str,
|
||||
span: Span,
|
||||
|
@ -104,17 +103,6 @@ fn get_first_entry_in_path(
|
|||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "which-support"))]
|
||||
fn get_first_entry_in_path(
|
||||
_item: &str,
|
||||
_span: Span,
|
||||
_cwd: impl AsRef<Path>,
|
||||
_paths: impl AsRef<OsStr>,
|
||||
) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "which-support")]
|
||||
fn get_all_entries_in_path(
|
||||
item: &str,
|
||||
span: Span,
|
||||
|
@ -129,16 +117,6 @@ fn get_all_entries_in_path(
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "which-support"))]
|
||||
fn get_all_entries_in_path(
|
||||
_item: &str,
|
||||
_span: Span,
|
||||
_cwd: impl AsRef<Path>,
|
||||
_paths: impl AsRef<OsStr>,
|
||||
) -> Vec<Value> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WhichArgs {
|
||||
applications: Vec<Spanned<String>>,
|
||||
|
|
|
@ -170,7 +170,10 @@ impl Command for Table {
|
|||
}),
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_int(3),
|
||||
"b" => Value::test_int(4),
|
||||
"b" => Value::test_list(vec![
|
||||
Value::test_int(4),
|
||||
Value::test_int(4),
|
||||
])
|
||||
}),
|
||||
])),
|
||||
},
|
||||
|
@ -184,7 +187,10 @@ impl Command for Table {
|
|||
}),
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_int(3),
|
||||
"b" => Value::test_int(4),
|
||||
"b" => Value::test_list(vec![
|
||||
Value::test_int(4),
|
||||
Value::test_int(4),
|
||||
])
|
||||
}),
|
||||
])),
|
||||
},
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
// Tests against table/structured data
|
||||
#[test]
|
||||
fn cal_full_year() {
|
||||
let actual = nu!("cal -y --full-year 2010 | first | to json -r");
|
||||
let actual = nu!("cal -t -y --full-year 2010 | first | to json -r");
|
||||
|
||||
let first_week_2010_json =
|
||||
r#"{"year":2010,"su":null,"mo":null,"tu":null,"we":null,"th":null,"fr":1,"sa":2}"#;
|
||||
|
@ -14,7 +15,7 @@ fn cal_full_year() {
|
|||
fn cal_february_2020_leap_year() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal -ym --full-year 2020 --month-names | where month == "february" | to json -r
|
||||
cal --as-table -ym --full-year 2020 --month-names | where month == "february" | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -27,7 +28,7 @@ fn cal_february_2020_leap_year() {
|
|||
fn cal_fr_the_thirteenths_in_2015() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2015 | default 0 fr | where fr == 13 | length
|
||||
cal --as-table --full-year 2015 | default 0 fr | where fr == 13 | length
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -38,7 +39,7 @@ fn cal_fr_the_thirteenths_in_2015() {
|
|||
fn cal_rows_in_2020() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2020 | length
|
||||
cal --as-table --full-year 2020 | length
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -49,7 +50,7 @@ fn cal_rows_in_2020() {
|
|||
fn cal_week_day_start_mo() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r
|
||||
cal --as-table --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -62,9 +63,43 @@ fn cal_week_day_start_mo() {
|
|||
fn cal_sees_pipeline_year() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 1020 | get mo | first 4 | to json -r
|
||||
cal --as-table --full-year 1020 | get mo | first 4 | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[null,3,10,17]");
|
||||
}
|
||||
|
||||
// Tests against default string output
|
||||
#[test]
|
||||
fn cal_is_string() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal | describe
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "string (stream)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cal_year_num_lines() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2024 | lines | length
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "68");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cal_week_start_string() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --week-start fr | lines | get 1 | split row '│' | get 2 | ansi strip | str trim
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "sa");
|
||||
}
|
||||
|
|
|
@ -31,12 +31,12 @@ fn detect_columns_with_legacy_and_flag_c() {
|
|||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
"[[c1,c3,c4,c5]; ['a b',c,d,e]]",
|
||||
"0..0",
|
||||
"0..1",
|
||||
),
|
||||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
"[[c1,c2,c3,c4]; [a,b,c,'d e']]",
|
||||
"(-2)..(-2)",
|
||||
"(-2)..(-1)",
|
||||
),
|
||||
(
|
||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||
|
@ -72,10 +72,10 @@ drwxr-xr-x 2 root root 4.0K Mar 20 08:28 =(char nl)
|
|||
drwxr-xr-x 4 root root 4.0K Mar 20 08:18 ~(char nl)
|
||||
-rw-r--r-- 1 root root 3.0K Mar 20 07:23 ~asdf(char nl)\"";
|
||||
let expected = "[
|
||||
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column8'];
|
||||
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20 08:28', '='],
|
||||
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20 08:18', '~'],
|
||||
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20 07:23', '~asdf']
|
||||
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column7', 'column8'];
|
||||
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20', '08:28', '='],
|
||||
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20', '08:18', '~'],
|
||||
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20', '07:23', '~asdf']
|
||||
]";
|
||||
let range = "5..6";
|
||||
let cmd = format!(
|
||||
|
|
|
@ -4,8 +4,9 @@ use nu_test_support::nu;
|
|||
fn error_label_works() {
|
||||
let actual = nu!("error make {msg:foo label:{text:unseen}}");
|
||||
|
||||
assert!(actual.err.contains("unseen"));
|
||||
assert!(actual.err.contains("╰──"));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("label at line 1, columns 1 to 10: unseen"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -37,7 +37,7 @@ fn given_fields_can_be_column_paths() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_variables() {
|
||||
fn cant_use_variables() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
|
@ -46,7 +46,8 @@ fn can_use_variables() {
|
|||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "nu is a new type of shell");
|
||||
// TODO SPAN: This has been removed during SpanId refactor
|
||||
assert!(actual.err.contains("Removed functionality"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -55,7 +56,7 @@ fn error_unmatched_brace() {
|
|||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| format pattern "{$it.package.name"
|
||||
| format pattern "{package.name"
|
||||
"#
|
||||
));
|
||||
|
||||
|
|
|
@ -76,6 +76,13 @@ fn into_filesize_wrong_negative_str_filesize() {
|
|||
assert!(actual.err.contains("can't convert string to filesize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_filesize_large_negative_str_filesize() {
|
||||
let actual = nu!("'-10000PiB' | into filesize");
|
||||
|
||||
assert!(actual.err.contains("can't convert string to filesize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_filesize_negative_str() {
|
||||
let actual = nu!("'-1' | into filesize");
|
||||
|
@ -104,6 +111,13 @@ fn into_filesize_wrong_positive_str_filesize() {
|
|||
assert!(actual.err.contains("can't convert string to filesize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_filesize_large_positive_str_filesize() {
|
||||
let actual = nu!("'+10000PiB' | into filesize");
|
||||
|
||||
assert!(actual.err.contains("can't convert string to filesize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_filesize_positive_str() {
|
||||
let actual = nu!("'+1' | into filesize");
|
||||
|
|
|
@ -2,7 +2,7 @@ use nu_test_support::nu;
|
|||
|
||||
#[test]
|
||||
fn length_columns_in_cal_table() {
|
||||
let actual = nu!("cal | columns | length");
|
||||
let actual = nu!("cal --as-table | columns | length");
|
||||
|
||||
assert_eq!(actual.out, "7");
|
||||
}
|
||||
|
|
|
@ -125,7 +125,6 @@ mod upsert;
|
|||
mod url;
|
||||
mod use_;
|
||||
mod where_;
|
||||
#[cfg(feature = "which-support")]
|
||||
mod which;
|
||||
mod while_;
|
||||
mod with_env;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#[cfg(not(windows))]
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
@ -17,7 +16,6 @@ fn better_empty_redirection() {
|
|||
assert!(!actual.out.contains('2'));
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn explicit_glob() {
|
||||
Playground::setup("external with explicit glob", |dirs, sandbox| {
|
||||
|
@ -30,15 +28,15 @@ fn explicit_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls | glob '*.txt' | length
|
||||
^nu --testbin cococo ('*.txt' | into glob)
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "2");
|
||||
assert!(actual.out.contains("D&D_volume_1.txt"));
|
||||
assert!(actual.out.contains("D&D_volume_2.txt"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn bare_word_expand_path_glob() {
|
||||
Playground::setup("bare word should do the expansion", |dirs, sandbox| {
|
||||
|
@ -51,7 +49,7 @@ fn bare_word_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^ls *.txt
|
||||
^nu --testbin cococo *.txt
|
||||
"
|
||||
));
|
||||
|
||||
|
@ -60,7 +58,6 @@ fn bare_word_expand_path_glob() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn backtick_expand_path_glob() {
|
||||
Playground::setup("backtick should do the expansion", |dirs, sandbox| {
|
||||
|
@ -73,7 +70,7 @@ fn backtick_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls `*.txt`
|
||||
^nu --testbin cococo `*.txt`
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -82,7 +79,6 @@ fn backtick_expand_path_glob() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn single_quote_does_not_expand_path_glob() {
|
||||
Playground::setup("single quote do not run the expansion", |dirs, sandbox| {
|
||||
|
@ -95,15 +91,14 @@ fn single_quote_does_not_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls '*.txt'
|
||||
^nu --testbin cococo '*.txt'
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("No such file or directory"));
|
||||
assert_eq!(actual.out, "*.txt");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn double_quote_does_not_expand_path_glob() {
|
||||
Playground::setup("double quote do not run the expansion", |dirs, sandbox| {
|
||||
|
@ -116,22 +111,21 @@ fn double_quote_does_not_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls "*.txt"
|
||||
^nu --testbin cococo "*.txt"
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("No such file or directory"));
|
||||
assert_eq!(actual.out, "*.txt");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^ls *.abc; echo done
|
||||
nu --testbin fail; echo done
|
||||
"
|
||||
));
|
||||
|
||||
|
@ -155,16 +149,51 @@ fn external_args_with_quoted() {
|
|||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn external_arg_with_long_flag_value_quoted() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
fn external_arg_with_option_like_embedded_quotes() {
|
||||
// TODO: would be nice to make this work with cococo, but arg parsing interferes
|
||||
Playground::setup(
|
||||
"external arg with option like embedded quotes",
|
||||
|dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo --foo='bar' -foo='bar'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "--foo=bar -foo=bar");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_with_non_option_like_embedded_quotes() {
|
||||
Playground::setup(
|
||||
"external arg with non option like embedded quotes",
|
||||
|dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^nu --testbin cococo foo='bar' 'foo'=bar
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "foo=bar foo=bar");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_with_string_interpolation() {
|
||||
Playground::setup("external arg with string interpolation", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo --foo='bar'
|
||||
^nu --testbin cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "--foo=bar");
|
||||
assert_eq!(actual.out, "foo=4 foo=4 foo=4");
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -200,6 +229,99 @@ fn external_command_escape_args() {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_ndots_args() {
|
||||
let actual = nu!(r#"
|
||||
nu --testbin cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
if cfg!(windows) {
|
||||
// Windows is a bit weird right now, where if ndots has to fix something it's going to
|
||||
// change everything to backslashes too. Would be good to fix that
|
||||
r"foo/. foo/.. foo\..\.. foo/./bar foo/../bar foo\..\..\bar ./bar ../bar ..\..\bar"
|
||||
} else {
|
||||
r"foo/. foo/.. foo/../.. foo/./bar foo/../bar foo/../../bar ./bar ../bar ../../bar"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_url_args() {
|
||||
// If ndots is not handled correctly, we can lose the double forward slashes that are needed
|
||||
// here
|
||||
let actual = nu!(r#"
|
||||
nu --testbin cococo http://example.com http://example.com/.../foo //foo
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"http://example.com http://example.com/.../foo //foo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(target_os = "linux"),
|
||||
ignore = "only runs on Linux, where controlling the HOME var is reliable"
|
||||
)]
|
||||
fn external_command_expand_tilde() {
|
||||
Playground::setup("external command expand tilde", |dirs, _| {
|
||||
// Make a copy of the nu executable that we can use
|
||||
let mut src = std::fs::File::open(nu_test_support::fs::binaries().join("nu"))
|
||||
.expect("failed to open nu");
|
||||
let mut dst = std::fs::File::create_new(dirs.test().join("test_nu"))
|
||||
.expect("failed to create test_nu file");
|
||||
std::io::copy(&mut src, &mut dst).expect("failed to copy data for nu binary");
|
||||
|
||||
// Make test_nu have the same permissions so that it's executable
|
||||
dst.set_permissions(
|
||||
src.metadata()
|
||||
.expect("failed to get nu metadata")
|
||||
.permissions(),
|
||||
)
|
||||
.expect("failed to set permissions on test_nu");
|
||||
|
||||
// Close the files
|
||||
drop(dst);
|
||||
drop(src);
|
||||
|
||||
let actual = nu!(
|
||||
envs: vec![
|
||||
("HOME".to_string(), dirs.test().to_string_lossy().into_owned()),
|
||||
],
|
||||
r#"
|
||||
^~/test_nu --testbin cococo hello
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "hello");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_expand_tilde() {
|
||||
Playground::setup("external arg expand tilde", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^nu --testbin cococo ~/foo ~/(2 + 2)
|
||||
"#
|
||||
));
|
||||
|
||||
let home = dirs_next::home_dir().expect("failed to find home dir");
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
format!(
|
||||
"{} {}",
|
||||
home.join("foo").display(),
|
||||
home.join("4").display()
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_not_expand_tilde_with_quotes() {
|
||||
Playground::setup(
|
||||
|
@ -231,21 +353,6 @@ fn external_command_receives_raw_binary_data() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^cargo asdf; echo done
|
||||
"
|
||||
));
|
||||
|
||||
assert!(!actual.out.contains("done"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn can_run_batch_files() {
|
||||
|
|
|
@ -255,7 +255,7 @@ fn substrings_the_input() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn substring_errors_if_start_index_is_greater_than_end_index() {
|
||||
fn substring_empty_if_start_index_is_greater_than_end_index() {
|
||||
Playground::setup("str_test_9", |dirs, sandbox| {
|
||||
sandbox.with_files(&[FileWithContent(
|
||||
"sample.toml",
|
||||
|
@ -270,12 +270,10 @@ fn substring_errors_if_start_index_is_greater_than_end_index() {
|
|||
r#"
|
||||
open sample.toml
|
||||
| str substring 6..4 fortune.teller.phone
|
||||
| get fortune.teller.phone
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("End must be greater than or equal to Start"))
|
||||
assert_eq!(actual.out, "")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -375,6 +373,21 @@ fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given(
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn substring_by_negative_index() {
|
||||
Playground::setup("str_test_13", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), "'apples' | str substring 0..-1",
|
||||
);
|
||||
assert_eq!(actual.out, "apples");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), "'apples' | str substring 0..<-1",
|
||||
);
|
||||
assert_eq!(actual.out, "apple");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_reverse() {
|
||||
let actual = nu!(r#"
|
||||
|
|
|
@ -906,7 +906,7 @@ fn test_cp_debug_default() {
|
|||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
if !actual
|
||||
.out
|
||||
.contains("copy offload: unknown, reflink: unsupported, sparse detection: no")
|
||||
.contains("copy offload: yes, reflink: unsupported, sparse detection: no")
|
||||
{
|
||||
panic!("{}", format!("Failure: stdout was \n{}", actual.out));
|
||||
}
|
||||
|
|
21
crates/nu-derive-value/Cargo.toml
Normal file
21
crates/nu-derive-value/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Macros implementation of #[derive(FromValue, IntoValue)]"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-derive-value"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
# we can only use exposed macros in doctests really,
|
||||
# so we cannot test anything useful in a doctest
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
proc-macro-error = { workspace = true }
|
||||
convert_case = { workspace = true }
|
21
crates/nu-derive-value/LICENSE
Normal file
21
crates/nu-derive-value/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2023 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
116
crates/nu-derive-value/src/attributes.rs
Normal file
116
crates/nu-derive-value/src/attributes.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use convert_case::Case;
|
||||
use syn::{spanned::Spanned, Attribute, Fields, LitStr};
|
||||
|
||||
use crate::{error::DeriveError, HELPER_ATTRIBUTE};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContainerAttributes {
|
||||
pub rename_all: Case,
|
||||
}
|
||||
|
||||
impl Default for ContainerAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rename_all: Case::Snake,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainerAttributes {
|
||||
pub fn parse_attrs<'a, M>(
|
||||
iter: impl Iterator<Item = &'a Attribute>,
|
||||
) -> Result<Self, DeriveError<M>> {
|
||||
let mut container_attrs = ContainerAttributes::default();
|
||||
for attr in filter(iter) {
|
||||
// This is a container to allow returning derive errors inside the parse_nested_meta fn.
|
||||
let mut err = Ok(());
|
||||
|
||||
attr.parse_nested_meta(|meta| {
|
||||
let ident = meta.path.require_ident()?;
|
||||
match ident.to_string().as_str() {
|
||||
"rename_all" => {
|
||||
// The matched case are all useful variants from `convert_case` with aliases
|
||||
// that `serde` uses.
|
||||
let case: LitStr = meta.value()?.parse()?;
|
||||
let case = match case.value().as_str() {
|
||||
"UPPER CASE" | "UPPER WITH SPACES CASE" => Case::Upper,
|
||||
"lower case" | "lower with spaces case" => Case::Lower,
|
||||
"Title Case" => Case::Title,
|
||||
"camelCase" => Case::Camel,
|
||||
"PascalCase" | "UpperCamelCase" => Case::Pascal,
|
||||
"snake_case" => Case::Snake,
|
||||
"UPPER_SNAKE_CASE" | "SCREAMING_SNAKE_CASE" => Case::UpperSnake,
|
||||
"kebab-case" => Case::Kebab,
|
||||
"COBOL-CASE" | "UPPER-KEBAB-CASE" | "SCREAMING-KEBAB-CASE" => {
|
||||
Case::Cobol
|
||||
}
|
||||
"Train-Case" => Case::Train,
|
||||
"flatcase" | "lowercase" => Case::Flat,
|
||||
"UPPERFLATCASE" | "UPPERCASE" => Case::UpperFlat,
|
||||
// Although very funny, we don't support `Case::{Toggle, Alternating}`,
|
||||
// as we see no real benefit.
|
||||
c => {
|
||||
err = Err(DeriveError::InvalidAttributeValue {
|
||||
value_span: case.span(),
|
||||
value: Box::new(c.to_string()),
|
||||
});
|
||||
return Ok(()); // We stored the err in `err`.
|
||||
}
|
||||
};
|
||||
container_attrs.rename_all = case;
|
||||
}
|
||||
ident => {
|
||||
err = Err(DeriveError::UnexpectedAttribute {
|
||||
meta_span: ident.span(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(DeriveError::Syn)?;
|
||||
|
||||
err?; // Shortcircuit here if `err` is holding some error.
|
||||
}
|
||||
|
||||
Ok(container_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter<'a>(
|
||||
iter: impl Iterator<Item = &'a Attribute>,
|
||||
) -> impl Iterator<Item = &'a Attribute> {
|
||||
iter.filter(|attr| attr.path().is_ident(HELPER_ATTRIBUTE))
|
||||
}
|
||||
|
||||
// The deny functions are built to easily deny the use of the helper attribute if used incorrectly.
|
||||
// As the usage of it gets more complex, these functions might be discarded or replaced.
|
||||
|
||||
/// Deny any attribute that uses the helper attribute.
|
||||
pub fn deny<M>(attrs: &[Attribute]) -> Result<(), DeriveError<M>> {
|
||||
match filter(attrs.iter()).next() {
|
||||
Some(attr) => Err(DeriveError::InvalidAttributePosition {
|
||||
attribute_span: attr.span(),
|
||||
}),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deny any attributes that uses the helper attribute on any field.
|
||||
pub fn deny_fields<M>(fields: &Fields) -> Result<(), DeriveError<M>> {
|
||||
match fields {
|
||||
Fields::Named(fields) => {
|
||||
for field in fields.named.iter() {
|
||||
deny(&field.attrs)?;
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
for field in fields.unnamed.iter() {
|
||||
deny(&field.attrs)?;
|
||||
}
|
||||
}
|
||||
Fields::Unit => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
82
crates/nu-derive-value/src/error.rs
Normal file
82
crates/nu-derive-value/src/error.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use std::{any, fmt::Debug, marker::PhantomData};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use proc_macro_error::{Diagnostic, Level};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DeriveError<M> {
|
||||
/// Marker variant, makes the `M` generic parameter valid.
|
||||
_Marker(PhantomData<M>),
|
||||
|
||||
/// Parsing errors thrown by `syn`.
|
||||
Syn(syn::parse::Error),
|
||||
|
||||
/// `syn::DeriveInput` was a union, currently not supported
|
||||
UnsupportedUnions,
|
||||
|
||||
/// Only plain enums are supported right now.
|
||||
UnsupportedEnums { fields_span: Span },
|
||||
|
||||
/// Found a `#[nu_value(x)]` attribute where `x` is unexpected.
|
||||
UnexpectedAttribute { meta_span: Span },
|
||||
|
||||
/// Found a `#[nu_value(x)]` attribute at a invalid position.
|
||||
InvalidAttributePosition { attribute_span: Span },
|
||||
|
||||
/// Found a valid `#[nu_value(x)]` attribute but the passed values is invalid.
|
||||
InvalidAttributeValue {
|
||||
value_span: Span,
|
||||
value: Box<dyn Debug>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<M> From<DeriveError<M>> for Diagnostic {
|
||||
fn from(value: DeriveError<M>) -> Self {
|
||||
let derive_name = any::type_name::<M>().split("::").last().expect("not empty");
|
||||
match value {
|
||||
DeriveError::_Marker(_) => panic!("used marker variant"),
|
||||
|
||||
DeriveError::Syn(e) => Diagnostic::spanned(e.span(), Level::Error, e.to_string()),
|
||||
|
||||
DeriveError::UnsupportedUnions => Diagnostic::new(
|
||||
Level::Error,
|
||||
format!("`{derive_name}` cannot be derived from unions"),
|
||||
)
|
||||
.help("consider refactoring to a struct".to_string())
|
||||
.note("if you really need a union, consider opening an issue on Github".to_string()),
|
||||
|
||||
DeriveError::UnsupportedEnums { fields_span } => Diagnostic::spanned(
|
||||
fields_span,
|
||||
Level::Error,
|
||||
format!("`{derive_name}` can only be derived from plain enums"),
|
||||
)
|
||||
.help(
|
||||
"consider refactoring your data type to a struct with a plain enum as a field"
|
||||
.to_string(),
|
||||
)
|
||||
.note("more complex enums could be implemented in the future".to_string()),
|
||||
|
||||
DeriveError::InvalidAttributePosition { attribute_span } => Diagnostic::spanned(
|
||||
attribute_span,
|
||||
Level::Error,
|
||||
"invalid attribute position".to_string(),
|
||||
)
|
||||
.help(format!(
|
||||
"check documentation for `{derive_name}` for valid placements"
|
||||
)),
|
||||
|
||||
DeriveError::UnexpectedAttribute { meta_span } => {
|
||||
Diagnostic::spanned(meta_span, Level::Error, "unknown attribute".to_string()).help(
|
||||
format!("check documentation for `{derive_name}` for valid attributes"),
|
||||
)
|
||||
}
|
||||
|
||||
DeriveError::InvalidAttributeValue { value_span, value } => {
|
||||
Diagnostic::spanned(value_span, Level::Error, format!("invalid value {value:?}"))
|
||||
.help(format!(
|
||||
"check documentation for `{derive_name}` for valid attribute values"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
567
crates/nu-derive-value/src/from.rs
Normal file
567
crates/nu-derive-value/src/from.rs
Normal file
|
@ -0,0 +1,567 @@
|
|||
use convert_case::Casing;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident,
|
||||
Type,
|
||||
};
|
||||
|
||||
use crate::attributes::{self, ContainerAttributes};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FromValue;
|
||||
type DeriveError = super::error::DeriveError<FromValue>;
|
||||
|
||||
/// Inner implementation of the `#[derive(FromValue)]` macro for structs and enums.
|
||||
///
|
||||
/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`.
|
||||
///
|
||||
/// This function directs the `FromValue` trait derivation to the correct implementation based on
|
||||
/// the input type:
|
||||
/// - For structs: [`derive_struct_from_value`]
|
||||
/// - For enums: [`derive_enum_from_value`]
|
||||
/// - Unions are not supported and will return an error.
|
||||
pub fn derive_from_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
||||
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||
match input.data {
|
||||
Data::Struct(data_struct) => Ok(derive_struct_from_value(
|
||||
input.ident,
|
||||
data_struct,
|
||||
input.generics,
|
||||
input.attrs,
|
||||
)?),
|
||||
Data::Enum(data_enum) => Ok(derive_enum_from_value(
|
||||
input.ident,
|
||||
data_enum,
|
||||
input.generics,
|
||||
input.attrs,
|
||||
)?),
|
||||
Data::Union(_) => Err(DeriveError::UnsupportedUnions),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `#[derive(FromValue)]` macro for structs.
|
||||
///
|
||||
/// This function ensures that the helper attribute is not used anywhere, as it is not supported for
|
||||
/// structs.
|
||||
/// Other than this, this function provides the impl signature for `FromValue`.
|
||||
/// The implementation for `FromValue::from_value` is handled by [`struct_from_value`] and the
|
||||
/// `FromValue::expected_type` is handled by [`struct_expected_type`].
|
||||
fn derive_struct_from_value(
|
||||
ident: Ident,
|
||||
data: DataStruct,
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
attributes::deny(&attrs)?;
|
||||
attributes::deny_fields(&data.fields)?;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let from_value_impl = struct_from_value(&data);
|
||||
let expected_type_impl = struct_expected_type(&data.fields);
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||
#from_value_impl
|
||||
#expected_type_impl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements `FromValue::from_value` for structs.
|
||||
///
|
||||
/// This function constructs the `from_value` function for structs.
|
||||
/// The implementation is straightforward as most of the heavy lifting is handled by
|
||||
/// `parse_value_via_fields`, and this function only needs to construct the signature around it.
|
||||
///
|
||||
/// For structs with named fields, this constructs a large return type where each field
|
||||
/// contains the implementation for that specific field.
|
||||
/// In structs with unnamed fields, a [`VecDeque`](std::collections::VecDeque) is used to load each
|
||||
/// field one after another, and the result is used to construct the tuple.
|
||||
/// For unit structs, this only checks if the input value is `Value::Nothing`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// These examples show what the macro would generate.
|
||||
///
|
||||
/// Struct with named fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Pet {
|
||||
/// name: String,
|
||||
/// age: u8,
|
||||
/// favorite_toy: Option<String>,
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Pet {
|
||||
/// fn from_value(
|
||||
/// v: nu_protocol::Value
|
||||
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||
/// let span = v.span();
|
||||
/// let mut record = v.into_record()?;
|
||||
/// std::result::Result::Ok(Pet {
|
||||
/// name: <String as nu_protocol::FromValue>::from_value(
|
||||
/// record
|
||||
/// .remove("name")
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string("name"),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?,
|
||||
/// age: <u8 as nu_protocol::FromValue>::from_value(
|
||||
/// record
|
||||
/// .remove("age")
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string("age"),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?,
|
||||
/// favorite_toy: record
|
||||
/// .remove("favorite_toy")
|
||||
/// .map(|v| <#ty as nu_protocol::FromValue>::from_value(v))
|
||||
/// .transpose()?
|
||||
/// .flatten(),
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Struct with unnamed fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Color(u8, u8, u8);
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Color {
|
||||
/// fn from_value(
|
||||
/// v: nu_protocol::Value
|
||||
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||
/// let span = v.span();
|
||||
/// let list = v.into_list()?;
|
||||
/// let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
||||
/// std::result::Result::Ok(Self(
|
||||
/// {
|
||||
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||
/// deque
|
||||
/// .pop_front()
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string(&0),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?
|
||||
/// },
|
||||
/// {
|
||||
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||
/// deque
|
||||
/// .pop_front()
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string(&1),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?
|
||||
/// },
|
||||
/// {
|
||||
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||
/// deque
|
||||
/// .pop_front()
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string(&2),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?
|
||||
/// }
|
||||
/// ))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Unit struct:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Unicorn;
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Unicorn {
|
||||
/// fn from_value(
|
||||
/// v: nu_protocol::Value
|
||||
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||
/// match v {
|
||||
/// nu_protocol::Value::Nothing {..} => Ok(Self),
|
||||
/// v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||
/// to_type: std::string::ToString::to_string(&<Self as nu_protocol::FromValue>::expected_type()),
|
||||
/// from_type: std::string::ToString::to_string(&v.get_type()),
|
||||
/// span: v.span(),
|
||||
/// help: std::option::Option::None
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||
let body = parse_value_via_fields(&data.fields, quote!(Self));
|
||||
quote! {
|
||||
fn from_value(
|
||||
v: nu_protocol::Value
|
||||
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||
#body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements `FromValue::expected_type` for structs.
|
||||
///
|
||||
/// This function constructs the `expected_type` function for structs.
|
||||
/// The type depends on the `fields`: named fields construct a record type with every key and type
|
||||
/// laid out.
|
||||
/// Unnamed fields construct a custom type with the name in the format like
|
||||
/// `list[type0, type1, type2]`.
|
||||
/// No fields expect the `Type::Nothing`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// These examples show what the macro would generate.
|
||||
///
|
||||
/// Struct with named fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Pet {
|
||||
/// name: String,
|
||||
/// age: u8,
|
||||
/// favorite_toy: Option<String>,
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Pet {
|
||||
/// fn expected_type() -> nu_protocol::Type {
|
||||
/// nu_protocol::Type::Record(
|
||||
/// std::vec![
|
||||
/// (
|
||||
/// std::string::ToString::to_string("name"),
|
||||
/// <String as nu_protocol::FromValue>::expected_type(),
|
||||
/// ),
|
||||
/// (
|
||||
/// std::string::ToString::to_string("age"),
|
||||
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||
/// ),
|
||||
/// (
|
||||
/// std::string::ToString::to_string("favorite_toy"),
|
||||
/// <Option<String> as nu_protocol::FromValue>::expected_type(),
|
||||
/// )
|
||||
/// ].into_boxed_slice()
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Struct with unnamed fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Color(u8, u8, u8);
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Color {
|
||||
/// fn expected_type() -> nu_protocol::Type {
|
||||
/// nu_protocol::Type::Custom(
|
||||
/// std::format!(
|
||||
/// "[{}, {}, {}]",
|
||||
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||
/// <u8 as nu_protocol::FromValue>::expected_type()
|
||||
/// )
|
||||
/// .into_boxed_str()
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Unit struct:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Unicorn;
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Color {
|
||||
/// fn expected_type() -> nu_protocol::Type {
|
||||
/// nu_protocol::Type::Nothing
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||
let ty = match fields {
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields.named.iter().map(|field| {
|
||||
let ident = field.ident.as_ref().expect("named has idents");
|
||||
let ident_s = ident.to_string();
|
||||
let ty = &field.ty;
|
||||
quote! {(
|
||||
std::string::ToString::to_string(#ident_s),
|
||||
<#ty as nu_protocol::FromValue>::expected_type(),
|
||||
)}
|
||||
});
|
||||
quote!(nu_protocol::Type::Record(
|
||||
std::vec![#(#fields),*].into_boxed_slice()
|
||||
))
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let mut iter = fields.unnamed.iter();
|
||||
let fields = fields.unnamed.iter().map(|field| {
|
||||
let ty = &field.ty;
|
||||
quote!(<#ty as nu_protocol::FromValue>::expected_type())
|
||||
});
|
||||
let mut template = String::new();
|
||||
template.push('[');
|
||||
if iter.next().is_some() {
|
||||
template.push_str("{}")
|
||||
}
|
||||
iter.for_each(|_| template.push_str(", {}"));
|
||||
template.push(']');
|
||||
quote! {
|
||||
nu_protocol::Type::Custom(
|
||||
std::format!(
|
||||
#template,
|
||||
#(#fields),*
|
||||
)
|
||||
.into_boxed_str()
|
||||
)
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote!(nu_protocol::Type::Nothing),
|
||||
};
|
||||
|
||||
quote! {
|
||||
fn expected_type() -> nu_protocol::Type {
|
||||
#ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `#[derive(FromValue)]` macro for enums.
|
||||
///
|
||||
/// This function constructs the implementation of the `FromValue` trait for enums.
|
||||
/// It is designed to be on the same level as [`derive_struct_from_value`], even though this
|
||||
/// function only provides the impl signature for `FromValue`.
|
||||
/// The actual implementation for `FromValue::from_value` is handled by [`enum_from_value`].
|
||||
///
|
||||
/// Since variants are difficult to type with the current type system, this function uses the
|
||||
/// default implementation for `expected_type`.
|
||||
fn derive_enum_from_value(
|
||||
ident: Ident,
|
||||
data: DataEnum,
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let from_value_impl = enum_from_value(&data, &attrs)?;
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||
#from_value_impl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements `FromValue::from_value` for enums.
|
||||
///
|
||||
/// This function constructs the `from_value` implementation for enums.
|
||||
/// It only accepts enums with unit variants, as it is currently unclear how other types of enums
|
||||
/// should be represented via a `Value`.
|
||||
/// This function checks that every field is a unit variant and constructs a match statement over
|
||||
/// all possible variants.
|
||||
/// The input value is expected to be a `Value::String` containing the name of the variant formatted
|
||||
/// as defined by the `#[nu_value(rename_all = "...")]` attribute.
|
||||
/// If no attribute is given, [`convert_case::Case::Snake`] is expected.
|
||||
///
|
||||
/// If no matching variant is found, `ShellError::CantConvert` is returned.
|
||||
///
|
||||
/// This is how such a derived implementation looks:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// enum Weather {
|
||||
/// Sunny,
|
||||
/// Cloudy,
|
||||
/// Raining
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::IntoValue for Weather {
|
||||
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
/// let span = v.span();
|
||||
/// let ty = v.get_type();
|
||||
///
|
||||
/// let s = v.into_string()?;
|
||||
/// match s.as_str() {
|
||||
/// "sunny" => std::result::Ok(Self::Sunny),
|
||||
/// "cloudy" => std::result::Ok(Self::Cloudy),
|
||||
/// "raining" => std::result::Ok(Self::Raining),
|
||||
/// _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||
/// to_type: std::string::ToString::to_string(
|
||||
/// &<Self as nu_protocol::FromValue>::expected_type()
|
||||
/// ),
|
||||
/// from_type: std::string::ToString::to_string(&ty),
|
||||
/// span: span,help: std::option::Option::None,
|
||||
/// }),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result<TokenStream2, DeriveError> {
|
||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||
let arms: Vec<TokenStream2> = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
attributes::deny(&variant.attrs)?;
|
||||
let ident = &variant.ident;
|
||||
let ident_s = format!("{ident}")
|
||||
.as_str()
|
||||
.to_case(container_attrs.rename_all);
|
||||
match &variant.fields {
|
||||
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||
fields_span: fields.span(),
|
||||
}),
|
||||
Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums {
|
||||
fields_span: fields.span(),
|
||||
}),
|
||||
Fields::Unit => Ok(quote!(#ident_s => std::result::Result::Ok(Self::#ident))),
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
fn from_value(
|
||||
v: nu_protocol::Value
|
||||
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||
let span = v.span();
|
||||
let ty = v.get_type();
|
||||
|
||||
let s = v.into_string()?;
|
||||
match s.as_str() {
|
||||
#(#arms,)*
|
||||
_ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||
to_type: std::string::ToString::to_string(
|
||||
&<Self as nu_protocol::FromValue>::expected_type()
|
||||
),
|
||||
from_type: std::string::ToString::to_string(&ty),
|
||||
span: span,
|
||||
help: std::option::Option::None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a `Value` into self.
|
||||
///
|
||||
/// This function handles the actual parsing of a `Value` into self.
|
||||
/// It takes two parameters: `fields` and `self_ident`.
|
||||
/// The `fields` parameter determines the expected type of `Value`: named fields expect a
|
||||
/// `Value::Record`, unnamed fields expect a `Value::List`, and a unit expects `Value::Nothing`.
|
||||
///
|
||||
/// For named fields, the `fields` parameter indicates which field in the record corresponds to
|
||||
/// which struct field.
|
||||
/// For both named and unnamed fields, it also helps cast the type into a `FromValue` type.
|
||||
/// This approach maintains
|
||||
/// [hygiene](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene).
|
||||
///
|
||||
/// The `self_ident` parameter is used to describe the identifier of the returned value.
|
||||
/// For structs, `Self` is usually sufficient, but for enums, `Self::Variant` may be needed in the
|
||||
/// future.
|
||||
///
|
||||
/// This function is more complex than the equivalent for `IntoValue` due to error handling
|
||||
/// requirements.
|
||||
/// For missing fields, `ShellError::CantFindColumn` is used, and for unit structs,
|
||||
/// `ShellError::CantConvert` is used.
|
||||
/// The implementation avoids local variables for fields to prevent accidental shadowing, ensuring
|
||||
/// that poorly named fields don't cause issues.
|
||||
/// While this style is not typically recommended in handwritten Rust, it is acceptable for code
|
||||
/// generation.
|
||||
fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenStream2 {
|
||||
match fields {
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields.named.iter().map(|field| {
|
||||
let ident = field.ident.as_ref().expect("named has idents");
|
||||
let ident_s = ident.to_string();
|
||||
let ty = &field.ty;
|
||||
match type_is_option(ty) {
|
||||
true => quote! {
|
||||
#ident: record
|
||||
.remove(#ident_s)
|
||||
.map(|v| <#ty as nu_protocol::FromValue>::from_value(v))
|
||||
.transpose()?
|
||||
.flatten()
|
||||
},
|
||||
|
||||
false => quote! {
|
||||
#ident: <#ty as nu_protocol::FromValue>::from_value(
|
||||
record
|
||||
.remove(#ident_s)
|
||||
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
col_name: std::string::ToString::to_string(#ident_s),
|
||||
span: std::option::Option::None,
|
||||
src_span: span
|
||||
})?,
|
||||
)?
|
||||
},
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
let span = v.span();
|
||||
let mut record = v.into_record()?;
|
||||
std::result::Result::Ok(#self_ident {#(#fields),*})
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let fields = fields.unnamed.iter().enumerate().map(|(i, field)| {
|
||||
let ty = &field.ty;
|
||||
quote! {{
|
||||
<#ty as nu_protocol::FromValue>::from_value(
|
||||
deque
|
||||
.pop_front()
|
||||
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
col_name: std::string::ToString::to_string(&#i),
|
||||
span: std::option::Option::None,
|
||||
src_span: span
|
||||
})?,
|
||||
)?
|
||||
}}
|
||||
});
|
||||
quote! {
|
||||
let span = v.span();
|
||||
let list = v.into_list()?;
|
||||
let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
||||
std::result::Result::Ok(#self_ident(#(#fields),*))
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote! {
|
||||
match v {
|
||||
nu_protocol::Value::Nothing {..} => Ok(#self_ident),
|
||||
v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||
to_type: std::string::ToString::to_string(&<Self as nu_protocol::FromValue>::expected_type()),
|
||||
from_type: std::string::ToString::to_string(&v.get_type()),
|
||||
span: v.span(),
|
||||
help: std::option::Option::None
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const FULLY_QUALIFIED_OPTION: &str = "std::option::Option";
|
||||
const PARTIALLY_QUALIFIED_OPTION: &str = "option::Option";
|
||||
const PRELUDE_OPTION: &str = "Option";
|
||||
|
||||
/// Check if the field type is an `Option`.
|
||||
///
|
||||
/// This function checks if a given type is an `Option`.
|
||||
/// We assume that an `Option` is [`std::option::Option`] because we can't see the whole code and
|
||||
/// can't ask the compiler itself.
|
||||
/// If the `Option` type isn't `std::option::Option`, the user will get a compile error due to a
|
||||
/// type mismatch.
|
||||
/// It's very unusual for people to override `Option`, so this should rarely be an issue.
|
||||
///
|
||||
/// When [rust#63084](https://github.com/rust-lang/rust/issues/63084) is resolved, we can use
|
||||
/// [`std::any::type_name`] for a static assertion check to get a more direct error messages.
|
||||
fn type_is_option(ty: &Type) -> bool {
|
||||
let s = ty.to_token_stream().to_string();
|
||||
s.starts_with(PRELUDE_OPTION)
|
||||
|| s.starts_with(PARTIALLY_QUALIFIED_OPTION)
|
||||
|| s.starts_with(FULLY_QUALIFIED_OPTION)
|
||||
}
|
266
crates/nu-derive-value/src/into.rs
Normal file
266
crates/nu-derive-value/src/into.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
use convert_case::Casing;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident,
|
||||
Index,
|
||||
};
|
||||
|
||||
use crate::attributes::{self, ContainerAttributes};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IntoValue;
|
||||
type DeriveError = super::error::DeriveError<IntoValue>;
|
||||
|
||||
/// Inner implementation of the `#[derive(IntoValue)]` macro for structs and enums.
|
||||
///
|
||||
/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`.
|
||||
///
|
||||
/// This function directs the `IntoValue` trait derivation to the correct implementation based on
|
||||
/// the input type:
|
||||
/// - For structs: [`struct_into_value`]
|
||||
/// - For enums: [`enum_into_value`]
|
||||
/// - Unions are not supported and will return an error.
|
||||
pub fn derive_into_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
||||
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||
match input.data {
|
||||
Data::Struct(data_struct) => Ok(struct_into_value(
|
||||
input.ident,
|
||||
data_struct,
|
||||
input.generics,
|
||||
input.attrs,
|
||||
)?),
|
||||
Data::Enum(data_enum) => Ok(enum_into_value(
|
||||
input.ident,
|
||||
data_enum,
|
||||
input.generics,
|
||||
input.attrs,
|
||||
)?),
|
||||
Data::Union(_) => Err(DeriveError::UnsupportedUnions),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `#[derive(IntoValue)]` macro for structs.
|
||||
///
|
||||
/// Automatically derives the `IntoValue` trait for any struct where each field implements
|
||||
/// `IntoValue`.
|
||||
/// For structs with named fields, the derived implementation creates a `Value::Record` using the
|
||||
/// struct fields as keys.
|
||||
/// Each field value is converted using the `IntoValue::into_value` method.
|
||||
/// For structs with unnamed fields, this generates a `Value::List` with each field in the list.
|
||||
/// For unit structs, this generates `Value::Nothing`, because there is no data.
|
||||
///
|
||||
/// Note: The helper attribute `#[nu_value(...)]` is currently not allowed on structs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// These examples show what the macro would generate.
|
||||
///
|
||||
/// Struct with named fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Pet {
|
||||
/// name: String,
|
||||
/// age: u8,
|
||||
/// favorite_toy: Option<String>,
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::IntoValue for Pet {
|
||||
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
/// nu_protocol::Value::record(nu_protocol::record! {
|
||||
/// "name" => nu_protocol::IntoValue::into_value(self.name, span),
|
||||
/// "age" => nu_protocol::IntoValue::into_value(self.age, span),
|
||||
/// "favorite_toy" => nu_protocol::IntoValue::into_value(self.favorite_toy, span),
|
||||
/// }, span)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Struct with unnamed fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Color(u8, u8, u8);
|
||||
///
|
||||
/// impl nu_protocol::IntoValue for Color {
|
||||
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
/// nu_protocol::Value::list(vec![
|
||||
/// nu_protocol::IntoValue::into_value(self.0, span),
|
||||
/// nu_protocol::IntoValue::into_value(self.1, span),
|
||||
/// nu_protocol::IntoValue::into_value(self.2, span),
|
||||
/// ], span)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Unit struct:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// struct Unicorn;
|
||||
///
|
||||
/// impl nu_protocol::IntoValue for Unicorn {
|
||||
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
/// nu_protocol::Value::nothing(span)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn struct_into_value(
|
||||
ident: Ident,
|
||||
data: DataStruct,
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
attributes::deny(&attrs)?;
|
||||
attributes::deny_fields(&data.fields)?;
|
||||
let record = match &data.fields {
|
||||
Fields::Named(fields) => {
|
||||
let accessor = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().expect("named has idents"))
|
||||
.map(|ident| quote!(self.#ident));
|
||||
fields_return_value(&data.fields, accessor)
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let accessor = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(n, _)| Index::from(n))
|
||||
.map(|index| quote!(self.#index));
|
||||
fields_return_value(&data.fields, accessor)
|
||||
}
|
||||
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
||||
};
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
|
||||
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
#record
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements the `#[derive(IntoValue)]` macro for enums.
|
||||
///
|
||||
/// This function implements the derive macro `IntoValue` for enums.
|
||||
/// Currently, only unit enum variants are supported as it is not clear how other types of enums
|
||||
/// should be represented in a `Value`.
|
||||
/// For simple enums, we represent the enum as a `Value::String`. For other types of variants, we return an error.
|
||||
/// The variant name will be case-converted as described by the `#[nu_value(rename_all = "...")]` helper attribute.
|
||||
/// If no attribute is used, the default is `case_convert::Case::Snake`.
|
||||
/// The implementation matches over all variants, uses the appropriate variant name, and constructs a `Value::String`.
|
||||
///
|
||||
/// This is how such a derived implementation looks:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// enum Weather {
|
||||
/// Sunny,
|
||||
/// Cloudy,
|
||||
/// Raining
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::IntoValue for Weather {
|
||||
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
/// match self {
|
||||
/// Self::Sunny => nu_protocol::Value::string("sunny", span),
|
||||
/// Self::Cloudy => nu_protocol::Value::string("cloudy", span),
|
||||
/// Self::Raining => nu_protocol::Value::string("raining", span),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn enum_into_value(
|
||||
ident: Ident,
|
||||
data: DataEnum,
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||
let arms: Vec<TokenStream2> = data
|
||||
.variants
|
||||
.into_iter()
|
||||
.map(|variant| {
|
||||
attributes::deny(&variant.attrs)?;
|
||||
let ident = variant.ident;
|
||||
let ident_s = format!("{ident}")
|
||||
.as_str()
|
||||
.to_case(container_attrs.rename_all);
|
||||
match &variant.fields {
|
||||
// In the future we can implement more complexe enums here.
|
||||
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||
fields_span: fields.span(),
|
||||
}),
|
||||
Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums {
|
||||
fields_span: fields.span(),
|
||||
}),
|
||||
Fields::Unit => {
|
||||
Ok(quote!(Self::#ident => nu_protocol::Value::string(#ident_s, span)))
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
Ok(quote! {
|
||||
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
|
||||
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
match self {
|
||||
#(#arms,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs the final `Value` that the macro generates.
|
||||
///
|
||||
/// This function handles the construction of the final `Value` that the macro generates.
|
||||
/// It is currently only used for structs but may be used for enums in the future.
|
||||
/// The function takes two parameters: the `fields`, which allow iterating over each field of a data
|
||||
/// type, and the `accessor`.
|
||||
/// The fields determine whether we need to generate a `Value::Record`, `Value::List`, or
|
||||
/// `Value::Nothing`.
|
||||
/// For named fields, they are also directly used to generate the record key.
|
||||
///
|
||||
/// The `accessor` parameter generalizes how the data is accessed.
|
||||
/// For named fields, this is usually the name of the fields preceded by `self` in a struct, and
|
||||
/// maybe something else for enums.
|
||||
/// For unnamed fields, this should be an iterator similar to the one with named fields, but
|
||||
/// accessing tuple fields, so we get `self.n`.
|
||||
/// For unit structs, this parameter is ignored.
|
||||
/// By using the accessor like this, we can have the same code for structs and enums with data
|
||||
/// variants in the future.
|
||||
fn fields_return_value(
|
||||
fields: &Fields,
|
||||
accessor: impl Iterator<Item = impl ToTokens>,
|
||||
) -> TokenStream2 {
|
||||
match fields {
|
||||
Fields::Named(fields) => {
|
||||
let items: Vec<TokenStream2> = fields
|
||||
.named
|
||||
.iter()
|
||||
.zip(accessor)
|
||||
.map(|(field, accessor)| {
|
||||
let ident = field.ident.as_ref().expect("named has idents");
|
||||
let field = ident.to_string();
|
||||
quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span))
|
||||
})
|
||||
.collect();
|
||||
quote! {
|
||||
nu_protocol::Value::record(nu_protocol::record! {
|
||||
#(#items),*
|
||||
}, span)
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let items =
|
||||
fields.unnamed.iter().zip(accessor).map(
|
||||
|(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)),
|
||||
);
|
||||
quote!(nu_protocol::Value::list(std::vec![#(#items),*], span))
|
||||
}
|
||||
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
||||
}
|
||||
}
|
69
crates/nu-derive-value/src/lib.rs
Normal file
69
crates/nu-derive-value/src/lib.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
//! Macro implementations of `#[derive(FromValue, IntoValue)]`.
|
||||
//!
|
||||
//! As this crate is a [`proc_macro`] crate, it is only allowed to export
|
||||
//! [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html).
|
||||
//! Therefore, it only exports [`IntoValue`] and [`FromValue`].
|
||||
//!
|
||||
//! To get documentation for other functions and types used in this crate, run
|
||||
//! `cargo doc -p nu-derive-value --document-private-items`.
|
||||
//!
|
||||
//! This crate uses a lot of
|
||||
//! [`proc_macro2::TokenStream`](https://docs.rs/proc-macro2/1.0.24/proc_macro2/struct.TokenStream.html)
|
||||
//! as `TokenStream2` to allow testing the behavior of the macros directly, including the output
|
||||
//! token stream or if the macro errors as expected.
|
||||
//! The tests for functionality can be found in `nu_protocol::value::test_derive`.
|
||||
//!
|
||||
//! This documentation is often less reference-heavy than typical Rust documentation.
|
||||
//! This is because this crate is a dependency for `nu_protocol`, and linking to it would create a
|
||||
//! cyclic dependency.
|
||||
//! Also all examples in the documentation aren't tested as this crate cannot be compiled as a
|
||||
//! normal library very easily.
|
||||
//! This might change in the future if cargo allows building a proc-macro crate differently for
|
||||
//! `cfg(doctest)` as they are already doing for `cfg(test)`.
|
||||
//!
|
||||
//! The generated code from the derive macros tries to be as
|
||||
//! [hygienic](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene) as possible.
|
||||
//! This ensures that the macro can be called anywhere without requiring specific imports.
|
||||
//! This results in obtuse code, which isn't recommended for manual, handwritten Rust
|
||||
//! but ensures that no other code may influence this generated code or vice versa.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::{proc_macro_error, Diagnostic};
|
||||
|
||||
mod attributes;
|
||||
mod error;
|
||||
mod from;
|
||||
mod into;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const HELPER_ATTRIBUTE: &str = "nu_value";
|
||||
|
||||
/// Derive macro generating an impl of the trait `IntoValue`.
|
||||
///
|
||||
/// For further information, see the docs on the trait itself.
|
||||
#[proc_macro_derive(IntoValue, attributes(nu_value))]
|
||||
#[proc_macro_error]
|
||||
pub fn derive_into_value(input: TokenStream) -> TokenStream {
|
||||
let input = TokenStream2::from(input);
|
||||
let output = match into::derive_into_value(input) {
|
||||
Ok(output) => output,
|
||||
Err(e) => Diagnostic::from(e).abort(),
|
||||
};
|
||||
TokenStream::from(output)
|
||||
}
|
||||
|
||||
/// Derive macro generating an impl of the trait `FromValue`.
|
||||
///
|
||||
/// For further information, see the docs on the trait itself.
|
||||
#[proc_macro_derive(FromValue, attributes(nu_value))]
|
||||
#[proc_macro_error]
|
||||
pub fn derive_from_value(input: TokenStream) -> TokenStream {
|
||||
let input = TokenStream2::from(input);
|
||||
let output = match from::derive_from_value(input) {
|
||||
Ok(output) => output,
|
||||
Err(e) => Diagnostic::from(e).abort(),
|
||||
};
|
||||
TokenStream::from(output)
|
||||
}
|
157
crates/nu-derive-value/src/tests.rs
Normal file
157
crates/nu-derive-value/src/tests.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
// These tests only check that the derive macros throw the relevant errors.
|
||||
// Functionality of the derived types is tested in nu_protocol::value::test_derive.
|
||||
|
||||
use crate::error::DeriveError;
|
||||
use crate::from::derive_from_value;
|
||||
use crate::into::derive_into_value;
|
||||
use quote::quote;
|
||||
|
||||
#[test]
|
||||
fn unsupported_unions() {
|
||||
let input = quote! {
|
||||
#[nu_value]
|
||||
union SomeUnion {
|
||||
f1: u32,
|
||||
f2: f32,
|
||||
}
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::UnsupportedUnions)),
|
||||
"expected `DeriveError::UnsupportedUnions`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::UnsupportedUnions)),
|
||||
"expected `DeriveError::UnsupportedUnions`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_enums() {
|
||||
let input = quote! {
|
||||
#[nu_value(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
enum ComplexEnum {
|
||||
Unit,
|
||||
Unnamed(u32, f32),
|
||||
Named {
|
||||
u: u32,
|
||||
f: f32,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::UnsupportedEnums { .. })),
|
||||
"expected `DeriveError::UnsupportedEnums`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::UnsupportedEnums { .. })),
|
||||
"expected `DeriveError::UnsupportedEnums`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_attribute() {
|
||||
let input = quote! {
|
||||
#[nu_value(what)]
|
||||
enum SimpleEnum {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deny_attribute_on_structs() {
|
||||
let input = quote! {
|
||||
#[nu_value]
|
||||
struct SomeStruct;
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deny_attribute_on_fields() {
|
||||
let input = quote! {
|
||||
struct SomeStruct {
|
||||
#[nu_value]
|
||||
field: ()
|
||||
}
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attribute_value() {
|
||||
let input = quote! {
|
||||
#[nu_value(rename_all = "CrazY-CasE")]
|
||||
enum SimpleEnum {
|
||||
A,
|
||||
B
|
||||
}
|
||||
};
|
||||
|
||||
let from_res = derive_from_value(input.clone());
|
||||
assert!(
|
||||
matches!(from_res, Err(DeriveError::InvalidAttributeValue { .. })),
|
||||
"expected `DeriveError::InvalidAttributeValue`, got {:?}",
|
||||
from_res
|
||||
);
|
||||
|
||||
let into_res = derive_into_value(input);
|
||||
assert!(
|
||||
matches!(into_res, Err(DeriveError::InvalidAttributeValue { .. })),
|
||||
"expected `DeriveError::InvalidAttributeValue`, got {:?}",
|
||||
into_res
|
||||
);
|
||||
}
|
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
log = { workspace = true }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -392,6 +392,7 @@ fn compile_expression(
|
|||
Expr::Overlay(_) => Err(CompileError::Todo("Overlay")),
|
||||
Expr::Signature(_) => Err(CompileError::Todo("Signature")),
|
||||
Expr::StringInterpolation(_) => Err(CompileError::Todo("StringInterpolation")),
|
||||
Expr::GlobInterpolation(_, _) => Err(CompileError::Todo("GlobInterpolation")),
|
||||
Expr::Nothing => lit(builder, Literal::Nothing),
|
||||
Expr::Garbage => Err(CompileError::Garbage),
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use nu_protocol::{
|
|||
ast::{Argument, Call, Expr, Expression, RecordItem},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID},
|
||||
record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId,
|
||||
record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
|
@ -299,16 +299,37 @@ fn get_documentation(
|
|||
}
|
||||
|
||||
if let Some(result) = &example.result {
|
||||
let mut table_call = Call::new(Span::unknown());
|
||||
if example.example.ends_with("--collapse") {
|
||||
// collapse the result
|
||||
table_call.add_named((
|
||||
Spanned {
|
||||
item: "collapse".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
// expand the result
|
||||
table_call.add_named((
|
||||
Spanned {
|
||||
item: "expand".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
))
|
||||
}
|
||||
let table = engine_state
|
||||
.find_decl("table".as_bytes(), &[])
|
||||
.and_then(|decl_id| {
|
||||
let call = Call::new(Span::unknown());
|
||||
engine_state
|
||||
.get_decl(decl_id)
|
||||
.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&(&call).into(),
|
||||
&(&table_call).into(),
|
||||
PipelineData::Value(result.clone(), None),
|
||||
)
|
||||
.ok()
|
||||
|
|
|
@ -208,11 +208,13 @@ fn eval_external(
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
let decl_id = engine_state
|
||||
.find_decl("run-external".as_bytes(), &[])
|
||||
.ok_or(ShellError::ExternalNotSupported { span: head.span })?;
|
||||
.ok_or(ShellError::ExternalNotSupported {
|
||||
span: head.span(&engine_state),
|
||||
})?;
|
||||
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
|
||||
let mut call = Call::new(head.span);
|
||||
let mut call = Call::new(head.span(&engine_state));
|
||||
|
||||
call.add_positional(head.clone());
|
||||
|
||||
|
@ -712,7 +714,7 @@ impl Eval for EvalRuntime {
|
|||
args: &[ExternalArgument],
|
||||
_: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let span = head.span;
|
||||
let span = head.span(&engine_state);
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
||||
}
|
||||
|
@ -779,9 +781,11 @@ impl Eval for EvalRuntime {
|
|||
let var_info = engine_state.get_var(*var_id);
|
||||
if var_info.mutable {
|
||||
stack.add_var(*var_id, rhs);
|
||||
Ok(Value::nothing(lhs.span))
|
||||
Ok(Value::nothing(lhs.span(&engine_state)))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
||||
Err(ShellError::AssignmentRequiresMutableVar {
|
||||
lhs_span: lhs.span(&engine_state),
|
||||
})
|
||||
}
|
||||
}
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
|
@ -797,7 +801,7 @@ impl Eval for EvalRuntime {
|
|||
// Reject attempts to assign to the entire $env
|
||||
if cell_path.tail.is_empty() {
|
||||
return Err(ShellError::CannotReplaceEnv {
|
||||
span: cell_path.head.span,
|
||||
span: cell_path.head.span(&engine_state),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -837,15 +841,21 @@ impl Eval for EvalRuntime {
|
|||
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
|
||||
stack.add_var(*var_id, lhs);
|
||||
}
|
||||
Ok(Value::nothing(cell_path.head.span))
|
||||
Ok(Value::nothing(cell_path.head.span(&engine_state)))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
||||
Err(ShellError::AssignmentRequiresMutableVar {
|
||||
lhs_span: lhs.span(&engine_state),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
_ => Err(ShellError::AssignmentRequiresVar {
|
||||
lhs_span: lhs.span(&engine_state),
|
||||
}),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
_ => Err(ShellError::AssignmentRequiresVar {
|
||||
lhs_span: lhs.span(&engine_state),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -882,8 +892,8 @@ impl Eval for EvalRuntime {
|
|||
Ok(Value::string(name, span))
|
||||
}
|
||||
|
||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||
Ok(Value::nothing(expr.span))
|
||||
fn unreachable(engine_state: &EngineState, expr: &Expression) -> Result<Value, ShellError> {
|
||||
Ok(Value::nothing(expr.span(&engine_state)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-explore"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -32,4 +32,4 @@ ansi-str = { workspace = true }
|
|||
unicode-width = { workspace = true }
|
||||
lscolors = { workspace = true, default-features = false, features = [
|
||||
"nu-ansi-term",
|
||||
] }
|
||||
] }
|
|
@ -27,7 +27,7 @@ impl NuCmd {
|
|||
}
|
||||
|
||||
impl ViewCommand for NuCmd {
|
||||
type View = NuView<'static>;
|
||||
type View = NuView;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
Self::NAME
|
||||
|
@ -72,12 +72,12 @@ impl ViewCommand for NuCmd {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum NuView<'a> {
|
||||
Records(RecordView<'a>),
|
||||
pub enum NuView {
|
||||
Records(RecordView),
|
||||
Preview(Preview),
|
||||
}
|
||||
|
||||
impl View for NuView<'_> {
|
||||
impl View for NuView {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
match self {
|
||||
NuView::Records(v) => v.draw(f, area, cfg, layout),
|
||||
|
|
|
@ -30,7 +30,7 @@ impl TableCmd {
|
|||
}
|
||||
|
||||
impl ViewCommand for TableCmd {
|
||||
type View = RecordView<'static>;
|
||||
type View = RecordView;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
Self::NAME
|
||||
|
|
|
@ -22,7 +22,7 @@ impl TryCmd {
|
|||
}
|
||||
|
||||
impl ViewCommand for TryCmd {
|
||||
type View = TryView<'static>;
|
||||
type View = TryView;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
Self::NAME
|
||||
|
|
|
@ -23,23 +23,20 @@ use nu_protocol::{
|
|||
Config, Record, Span, Value,
|
||||
};
|
||||
use ratatui::{layout::Rect, widgets::Block};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use self::table_widget::Orientation;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordView<'a> {
|
||||
layer_stack: Vec<RecordLayer<'a>>,
|
||||
pub struct RecordView {
|
||||
layer_stack: Vec<RecordLayer>,
|
||||
mode: UIMode,
|
||||
orientation: Orientation,
|
||||
cfg: ExploreConfig,
|
||||
}
|
||||
|
||||
impl<'a> RecordView<'a> {
|
||||
pub fn new(
|
||||
columns: impl Into<Cow<'a, [String]>>,
|
||||
records: impl Into<Cow<'a, [Vec<Value>]>>,
|
||||
) -> Self {
|
||||
impl RecordView {
|
||||
pub fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
|
||||
Self {
|
||||
layer_stack: vec![RecordLayer::new(columns, records)],
|
||||
mode: UIMode::View,
|
||||
|
@ -64,13 +61,13 @@ impl<'a> RecordView<'a> {
|
|||
}
|
||||
|
||||
// todo: rename to get_layer
|
||||
pub fn get_layer_last(&self) -> &RecordLayer<'a> {
|
||||
pub fn get_layer_last(&self) -> &RecordLayer {
|
||||
self.layer_stack
|
||||
.last()
|
||||
.expect("we guarantee that 1 entry is always in a list")
|
||||
}
|
||||
|
||||
pub fn get_layer_last_mut(&mut self) -> &mut RecordLayer<'a> {
|
||||
pub fn get_layer_last_mut(&mut self) -> &mut RecordLayer {
|
||||
self.layer_stack
|
||||
.last_mut()
|
||||
.expect("we guarantee that 1 entry is always in a list")
|
||||
|
@ -134,32 +131,39 @@ impl<'a> RecordView<'a> {
|
|||
Orientation::Left => (column, row),
|
||||
};
|
||||
|
||||
if layer.records.len() > row && layer.records[row].len() > column {
|
||||
layer.records[row][column].clone()
|
||||
} else {
|
||||
Value::nothing(Span::unknown())
|
||||
if row >= layer.record_values.len() || column >= layer.column_names.len() {
|
||||
// actually must never happen; unless cursor works incorrectly
|
||||
// if being sure about cursor it can be deleted;
|
||||
return Value::nothing(Span::unknown());
|
||||
}
|
||||
|
||||
layer.record_values[row][column].clone()
|
||||
}
|
||||
|
||||
/// Create a table widget.
|
||||
/// WARNING: this is currently really slow on large data sets.
|
||||
/// It creates a string representation of every cell in the table and looks at every row for lscolorize.
|
||||
fn create_table_widget(&'a self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||
let layer = self.get_layer_last();
|
||||
let mut data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.style_computer);
|
||||
lscolorize(&layer.columns, &mut data, cfg.lscolors);
|
||||
|
||||
let headers = layer.columns.as_ref();
|
||||
fn create_table_widget<'a>(&'a mut self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||
let style = self.cfg.table;
|
||||
let style_computer = cfg.style_computer;
|
||||
let Position { row, column } = self.get_window_origin();
|
||||
|
||||
let layer = self.get_layer_last_mut();
|
||||
if layer.record_text.is_none() {
|
||||
let mut data =
|
||||
convert_records_to_string(&layer.record_values, cfg.nu_config, cfg.style_computer);
|
||||
lscolorize(&layer.column_names, &mut data, cfg.lscolors);
|
||||
|
||||
layer.record_text = Some(data);
|
||||
}
|
||||
|
||||
let headers = &layer.column_names;
|
||||
let data = layer.record_text.as_ref().expect("always ok");
|
||||
|
||||
TableWidget::new(
|
||||
headers,
|
||||
data,
|
||||
style_computer,
|
||||
row,
|
||||
column,
|
||||
self.cfg.table,
|
||||
style,
|
||||
layer.orientation,
|
||||
)
|
||||
}
|
||||
|
@ -197,7 +201,7 @@ impl<'a> RecordView<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl View for RecordView<'_> {
|
||||
impl View for RecordView {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
let mut table_layout = TableWidgetState::default();
|
||||
// TODO: creating the table widget is O(N) where N is the number of cells in the grid.
|
||||
|
@ -265,7 +269,7 @@ impl View for RecordView<'_> {
|
|||
let style_computer = StyleComputer::new(&dummy_engine_state, &dummy_stack, HashMap::new());
|
||||
|
||||
let data = convert_records_to_string(
|
||||
&self.get_layer_last().records,
|
||||
&self.get_layer_last().record_values,
|
||||
&nu_protocol::Config::default(),
|
||||
&style_computer,
|
||||
);
|
||||
|
@ -274,7 +278,7 @@ impl View for RecordView<'_> {
|
|||
}
|
||||
|
||||
fn show_data(&mut self, pos: usize) -> bool {
|
||||
let data = &self.get_layer_last().records;
|
||||
let data = &self.get_layer_last().record_values;
|
||||
|
||||
let mut i = 0;
|
||||
for (row, cells) in data.iter().enumerate() {
|
||||
|
@ -332,30 +336,35 @@ enum UIMode {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordLayer<'a> {
|
||||
columns: Cow<'a, [String]>,
|
||||
records: Cow<'a, [Vec<Value>]>,
|
||||
pub struct RecordLayer {
|
||||
column_names: Vec<String>,
|
||||
// These are the raw records in the current layer. The sole reason we keep this around is so we can return the original value
|
||||
// if it's being peeked. Otherwise we could accept an iterator over it.
|
||||
// or if it could be Clonable we could do that anyway;
|
||||
// cause it would keep memory footprint lower while keep everything working
|
||||
// (yee would make return O(n); we would need to traverse iterator once again; but maybe worth it)
|
||||
record_values: Vec<Vec<Value>>,
|
||||
// This is the text representation of the record values (the actual text that will be displayed to users).
|
||||
// It's an Option because we need configuration to set it and we (currently) don't have access to configuration when things are created.
|
||||
record_text: Option<Vec<Vec<NuText>>>,
|
||||
orientation: Orientation,
|
||||
name: Option<String>,
|
||||
was_transposed: bool,
|
||||
cursor: WindowCursor2D,
|
||||
}
|
||||
|
||||
impl<'a> RecordLayer<'a> {
|
||||
fn new(
|
||||
columns: impl Into<Cow<'a, [String]>>,
|
||||
records: impl Into<Cow<'a, [Vec<Value>]>>,
|
||||
) -> Self {
|
||||
let columns = columns.into();
|
||||
let records = records.into();
|
||||
|
||||
impl RecordLayer {
|
||||
fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
|
||||
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
||||
let cursor =
|
||||
WindowCursor2D::new(records.len(), columns.len()).expect("Failed to create cursor");
|
||||
|
||||
let column_names = columns.iter().map(|s| strip_string(s)).collect();
|
||||
|
||||
Self {
|
||||
columns,
|
||||
records,
|
||||
column_names,
|
||||
record_values: records,
|
||||
record_text: None,
|
||||
cursor,
|
||||
orientation: Orientation::Top,
|
||||
name: None,
|
||||
|
@ -369,21 +378,21 @@ impl<'a> RecordLayer<'a> {
|
|||
|
||||
fn count_rows(&self) -> usize {
|
||||
match self.orientation {
|
||||
Orientation::Top => self.records.len(),
|
||||
Orientation::Left => self.columns.len(),
|
||||
Orientation::Top => self.record_values.len(),
|
||||
Orientation::Left => self.column_names.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn count_columns(&self) -> usize {
|
||||
match self.orientation {
|
||||
Orientation::Top => self.columns.len(),
|
||||
Orientation::Left => self.records.len(),
|
||||
Orientation::Top => self.column_names.len(),
|
||||
Orientation::Left => self.record_values.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_column_header(&self) -> Option<String> {
|
||||
let col = self.cursor.column();
|
||||
self.columns.get(col).map(|header| header.to_string())
|
||||
self.column_names.get(col).map(|header| header.to_string())
|
||||
}
|
||||
|
||||
fn reset_cursor(&mut self) {
|
||||
|
@ -570,13 +579,13 @@ fn handle_key_event_cursor_mode(
|
|||
}
|
||||
}
|
||||
|
||||
fn create_layer(value: Value) -> Result<RecordLayer<'static>> {
|
||||
fn create_layer(value: Value) -> Result<RecordLayer> {
|
||||
let (columns, values) = collect_input(value)?;
|
||||
|
||||
Ok(RecordLayer::new(columns, values))
|
||||
}
|
||||
|
||||
fn push_layer(view: &mut RecordView<'_>, mut next_layer: RecordLayer<'static>) {
|
||||
fn push_layer(view: &mut RecordView, mut next_layer: RecordLayer) {
|
||||
let layer = view.get_layer_last();
|
||||
let header = layer.get_column_header();
|
||||
|
||||
|
@ -599,9 +608,9 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 {
|
|||
}
|
||||
|
||||
/// scroll to the end of the data
|
||||
fn tail_data(state: &mut RecordView<'_>, page_size: usize) {
|
||||
fn tail_data(state: &mut RecordView, page_size: usize) {
|
||||
let layer = state.get_layer_last_mut();
|
||||
let count_rows = layer.records.len();
|
||||
let count_rows = layer.record_values.len();
|
||||
if count_rows > page_size {
|
||||
layer
|
||||
.cursor
|
||||
|
@ -620,6 +629,7 @@ fn convert_records_to_string(
|
|||
row.iter()
|
||||
.map(|value| {
|
||||
let text = value.clone().to_abbreviated_string(cfg);
|
||||
let text = strip_string(&text);
|
||||
let float_precision = cfg.float_precision as usize;
|
||||
|
||||
make_styled_string(style_computer, text, Some(value), float_precision)
|
||||
|
@ -649,12 +659,16 @@ fn build_last_value(v: &RecordView) -> Value {
|
|||
fn build_table_as_list(v: &RecordView) -> Value {
|
||||
let layer = v.get_layer_last();
|
||||
|
||||
let cols = &layer.columns;
|
||||
let vals = layer
|
||||
.records
|
||||
.record_values
|
||||
.iter()
|
||||
.map(|vals| {
|
||||
let record = cols.iter().cloned().zip(vals.iter().cloned()).collect();
|
||||
let record = layer
|
||||
.column_names
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(vals.iter().cloned())
|
||||
.collect();
|
||||
Value::record(record, NuSpan::unknown())
|
||||
})
|
||||
.collect();
|
||||
|
@ -665,16 +679,15 @@ fn build_table_as_list(v: &RecordView) -> Value {
|
|||
fn build_table_as_record(v: &RecordView) -> Value {
|
||||
let layer = v.get_layer_last();
|
||||
|
||||
let record = if let Some(row) = layer.records.first() {
|
||||
layer
|
||||
.columns
|
||||
let mut record = Record::new();
|
||||
if let Some(row) = layer.record_values.first() {
|
||||
record = layer
|
||||
.column_names
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(row.iter().cloned())
|
||||
.collect()
|
||||
} else {
|
||||
Record::new()
|
||||
};
|
||||
.collect();
|
||||
}
|
||||
|
||||
Value::record(record, NuSpan::unknown())
|
||||
}
|
||||
|
@ -708,47 +721,67 @@ fn get_percentage(value: usize, max: usize) -> usize {
|
|||
((value as f32 / max as f32) * 100.0).floor() as usize
|
||||
}
|
||||
|
||||
fn transpose_table(layer: &mut RecordLayer<'_>) {
|
||||
let count_rows = layer.records.len();
|
||||
let count_columns = layer.columns.len();
|
||||
|
||||
fn transpose_table(layer: &mut RecordLayer) {
|
||||
if layer.was_transposed {
|
||||
let data = match &mut layer.records {
|
||||
Cow::Owned(data) => data,
|
||||
Cow::Borrowed(_) => unreachable!("must never happen"),
|
||||
};
|
||||
|
||||
let headers = pop_first_column(data);
|
||||
let headers = headers
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
Value::String { val, .. } => val,
|
||||
_ => unreachable!("must never happen"),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let data = _transpose_table(data, count_rows, count_columns - 1);
|
||||
|
||||
layer.records = Cow::Owned(data);
|
||||
layer.columns = Cow::Owned(headers);
|
||||
transpose_from(layer);
|
||||
} else {
|
||||
let mut data = _transpose_table(&layer.records, count_rows, count_columns);
|
||||
|
||||
for (column, column_name) in layer.columns.iter().enumerate() {
|
||||
let value = Value::string(column_name, NuSpan::unknown());
|
||||
|
||||
data[column].insert(0, value);
|
||||
}
|
||||
|
||||
layer.records = Cow::Owned(data);
|
||||
layer.columns = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect();
|
||||
transpose_to(layer);
|
||||
}
|
||||
|
||||
layer.was_transposed = !layer.was_transposed;
|
||||
}
|
||||
|
||||
fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
|
||||
let mut data = vec![Value::default(); values.len()];
|
||||
fn transpose_from(layer: &mut RecordLayer) {
|
||||
let count_rows = layer.record_values.len();
|
||||
let count_columns = layer.column_names.len();
|
||||
|
||||
if let Some(data) = &mut layer.record_text {
|
||||
pop_first_column(data);
|
||||
*data = _transpose_table(data, count_rows, count_columns - 1);
|
||||
}
|
||||
|
||||
let headers = pop_first_column(&mut layer.record_values);
|
||||
let headers = headers
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
Value::String { val, .. } => val,
|
||||
_ => unreachable!("must never happen"),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
|
||||
|
||||
layer.record_values = data;
|
||||
layer.column_names = headers;
|
||||
}
|
||||
|
||||
fn transpose_to(layer: &mut RecordLayer) {
|
||||
let count_rows = layer.record_values.len();
|
||||
let count_columns = layer.column_names.len();
|
||||
|
||||
if let Some(data) = &mut layer.record_text {
|
||||
*data = _transpose_table(data, count_rows, count_columns);
|
||||
for (column, column_name) in layer.column_names.iter().enumerate() {
|
||||
let value = (column_name.to_owned(), Default::default());
|
||||
data[column].insert(0, value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = _transpose_table(&layer.record_values, count_rows, count_columns);
|
||||
for (column, column_name) in layer.column_names.iter().enumerate() {
|
||||
let value = Value::string(column_name, NuSpan::unknown());
|
||||
data[column].insert(0, value);
|
||||
}
|
||||
|
||||
layer.record_values = data;
|
||||
layer.column_names = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect();
|
||||
}
|
||||
|
||||
fn pop_first_column<T>(values: &mut [Vec<T>]) -> Vec<T>
|
||||
where
|
||||
T: Default + Clone,
|
||||
{
|
||||
let mut data = vec![T::default(); values.len()];
|
||||
for (row, values) in values.iter_mut().enumerate() {
|
||||
data[row] = values.remove(0);
|
||||
}
|
||||
|
@ -756,12 +789,11 @@ fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
|
|||
data
|
||||
}
|
||||
|
||||
fn _transpose_table(
|
||||
values: &[Vec<Value>],
|
||||
count_rows: usize,
|
||||
count_columns: usize,
|
||||
) -> Vec<Vec<Value>> {
|
||||
let mut data = vec![vec![Value::default(); count_rows]; count_columns];
|
||||
fn _transpose_table<T>(values: &[Vec<T>], count_rows: usize, count_columns: usize) -> Vec<Vec<T>>
|
||||
where
|
||||
T: Clone + Default,
|
||||
{
|
||||
let mut data = vec![vec![T::default(); count_rows]; count_columns];
|
||||
for (row, values) in values.iter().enumerate() {
|
||||
for (column, value) in values.iter().enumerate() {
|
||||
data[column][row].clone_from(value);
|
||||
|
@ -770,3 +802,9 @@ fn _transpose_table(
|
|||
|
||||
data
|
||||
}
|
||||
|
||||
fn strip_string(text: &str) -> String {
|
||||
String::from_utf8(strip_ansi_escapes::strip(text))
|
||||
.map_err(|_| ())
|
||||
.unwrap_or_else(|_| text.to_owned())
|
||||
}
|
||||
|
|
|
@ -13,15 +13,12 @@ use ratatui::{
|
|||
text::Span,
|
||||
widgets::{Block, Borders, Paragraph, StatefulWidget, Widget},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{max, Ordering},
|
||||
};
|
||||
use std::cmp::{max, Ordering};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableWidget<'a> {
|
||||
columns: Cow<'a, [String]>,
|
||||
data: Cow<'a, [Vec<NuText>]>,
|
||||
columns: &'a [String],
|
||||
data: &'a [Vec<NuText>],
|
||||
index_row: usize,
|
||||
index_column: usize,
|
||||
config: TableConfig,
|
||||
|
@ -39,8 +36,8 @@ pub enum Orientation {
|
|||
impl<'a> TableWidget<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
columns: impl Into<Cow<'a, [String]>>,
|
||||
data: impl Into<Cow<'a, [Vec<NuText>]>>,
|
||||
columns: &'a [String],
|
||||
data: &'a [Vec<NuText>],
|
||||
style_computer: &'a StyleComputer<'a>,
|
||||
index_row: usize,
|
||||
index_column: usize,
|
||||
|
@ -48,8 +45,8 @@ impl<'a> TableWidget<'a> {
|
|||
header_position: Orientation,
|
||||
) -> Self {
|
||||
Self {
|
||||
columns: columns.into(),
|
||||
data: data.into(),
|
||||
columns,
|
||||
data,
|
||||
style_computer,
|
||||
index_row,
|
||||
index_column,
|
||||
|
@ -91,6 +88,7 @@ impl StatefulWidget for TableWidget<'_> {
|
|||
|
||||
// todo: refactoring these to methods as they have quite a bit in common.
|
||||
impl<'a> TableWidget<'a> {
|
||||
// header at the top; header is always 1 line
|
||||
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
let padding_l = self.config.column_padding_left as u16;
|
||||
let padding_r = self.config.column_padding_right as u16;
|
||||
|
@ -133,25 +131,16 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if show_index {
|
||||
let area = Rect::new(width, data_y, area.width, data_height);
|
||||
width += render_index(
|
||||
buf,
|
||||
area,
|
||||
Rect::new(width, data_y, area.width, data_height),
|
||||
self.style_computer,
|
||||
self.index_row,
|
||||
padding_l,
|
||||
padding_r,
|
||||
);
|
||||
|
||||
width += render_vertical_line_with_split(
|
||||
buf,
|
||||
width,
|
||||
data_y,
|
||||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
separator_s,
|
||||
);
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
// if there is more data than we can show, add an ellipsis to the column headers to hint at that
|
||||
|
@ -165,6 +154,11 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
for col in self.index_column..self.columns.len() {
|
||||
let need_split_line = state.count_columns > 0 && width < area.width;
|
||||
if need_split_line {
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
let mut column = create_column(data, col);
|
||||
let column_width = calculate_column_width(&column);
|
||||
|
||||
|
@ -197,22 +191,26 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if show_head {
|
||||
let mut header = [head_row_text(&head, self.style_computer)];
|
||||
let head_style = head_style(&head, self.style_computer);
|
||||
if head_width > use_space as usize {
|
||||
truncate_str(&mut header[0].0, use_space as usize)
|
||||
truncate_str(&mut head, use_space as usize)
|
||||
}
|
||||
let head_iter = [(&head, head_style)].into_iter();
|
||||
|
||||
// we don't change width here cause the whole column have the same width; so we add it when we print data
|
||||
let mut w = width;
|
||||
w += render_space(buf, w, head_y, 1, padding_l);
|
||||
w += render_column(buf, w, head_y, use_space, &header);
|
||||
w += render_column(buf, w, head_y, use_space, head_iter);
|
||||
w += render_space(buf, w, head_y, 1, padding_r);
|
||||
|
||||
let x = w - padding_r - use_space;
|
||||
state.layout.push(&header[0].0, x, head_y, use_space, 1);
|
||||
state.layout.push(&head, x, head_y, use_space, 1);
|
||||
}
|
||||
|
||||
let column_rows = column.iter().map(|(t, s)| (t, *s));
|
||||
|
||||
width += render_space(buf, width, data_y, data_height, padding_l);
|
||||
width += render_column(buf, width, data_y, use_space, &column);
|
||||
width += render_column(buf, width, data_y, use_space, column_rows);
|
||||
width += render_space(buf, width, data_y, data_height, padding_r);
|
||||
|
||||
for (row, (text, _)) in column.iter().enumerate() {
|
||||
|
@ -235,15 +233,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if width < area.width {
|
||||
width += render_vertical_line_with_split(
|
||||
buf,
|
||||
width,
|
||||
data_y,
|
||||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
separator_s,
|
||||
);
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
let rest = area.width.saturating_sub(width);
|
||||
|
@ -255,6 +245,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// header at the left; header is always 1 line
|
||||
fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
return;
|
||||
|
@ -305,10 +296,9 @@ impl<'a> TableWidget<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let columns = columns
|
||||
let columns_iter = columns
|
||||
.iter()
|
||||
.map(|s| head_row_text(s, self.style_computer))
|
||||
.collect::<Vec<_>>();
|
||||
.map(|s| (s.clone(), head_style(s, self.style_computer)));
|
||||
|
||||
if !show_index {
|
||||
let x = area.x + left_w;
|
||||
|
@ -326,12 +316,12 @@ impl<'a> TableWidget<'a> {
|
|||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, 1, padding_l);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_column(buf, x, area.y, columns_width as u16, &columns);
|
||||
left_w += render_column(buf, x, area.y, columns_width as u16, columns_iter);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, 1, padding_r);
|
||||
|
||||
let layout_x = left_w - padding_r - columns_width as u16;
|
||||
for (i, (text, _)) in columns.iter().enumerate() {
|
||||
for (i, text) in columns.iter().enumerate() {
|
||||
state
|
||||
.layout
|
||||
.push(text, layout_x, area.y + i as u16, columns_width as u16, 1);
|
||||
|
@ -354,6 +344,9 @@ impl<'a> TableWidget<'a> {
|
|||
state.count_rows = columns.len();
|
||||
state.count_columns = 0;
|
||||
|
||||
// note: is there a time where we would have more then 1 column?
|
||||
// seems like not really; cause it's literally KV table, or am I wrong?
|
||||
|
||||
for col in self.index_column..self.data.len() {
|
||||
let mut column =
|
||||
self.data[col][self.index_row..self.index_row + columns.len()].to_vec();
|
||||
|
@ -362,6 +355,13 @@ impl<'a> TableWidget<'a> {
|
|||
break;
|
||||
}
|
||||
|
||||
// see KV comment; this block might never got used
|
||||
let need_split_line = state.count_columns > 0 && left_w < area.width;
|
||||
if need_split_line {
|
||||
render_vertical_line(buf, area.x + left_w, area.y, area.height, separator_s);
|
||||
left_w += 1;
|
||||
}
|
||||
|
||||
let column_width = column_width as u16;
|
||||
let available = area.width - left_w;
|
||||
let is_last = col + 1 == self.data.len();
|
||||
|
@ -377,10 +377,12 @@ impl<'a> TableWidget<'a> {
|
|||
break;
|
||||
}
|
||||
|
||||
let head_rows = column.iter().map(|(t, s)| (t, *s));
|
||||
|
||||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, area.height, padding_l);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_column(buf, x, area.y, column_width, &column);
|
||||
left_w += render_column(buf, x, area.y, column_width, head_rows);
|
||||
let x = area.x + left_w;
|
||||
left_w += render_space(buf, x, area.y, area.height, padding_r);
|
||||
|
||||
|
@ -554,6 +556,51 @@ fn render_index(
|
|||
width
|
||||
}
|
||||
|
||||
fn render_split_line(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
height: u16,
|
||||
has_head: bool,
|
||||
style: NuStyle,
|
||||
) -> u16 {
|
||||
if has_head {
|
||||
render_vertical_split_line(buf, x, y, height, &[0], &[2], &[], style);
|
||||
} else {
|
||||
render_vertical_split_line(buf, x, y, height, &[], &[], &[], style);
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_vertical_split_line(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
height: u16,
|
||||
top_slit: &[u16],
|
||||
inner_slit: &[u16],
|
||||
bottom_slit: &[u16],
|
||||
style: NuStyle,
|
||||
) -> u16 {
|
||||
render_vertical_line(buf, x, y, height, style);
|
||||
|
||||
for &y in top_slit {
|
||||
render_top_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
for &y in inner_slit {
|
||||
render_inner_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
for &y in bottom_slit {
|
||||
render_bottom_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
fn render_vertical_line_with_split(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
|
@ -619,14 +666,14 @@ fn repeat_vertical(
|
|||
c: char,
|
||||
style: TextStyle,
|
||||
) {
|
||||
let text = std::iter::repeat(c)
|
||||
.take(width as usize)
|
||||
.collect::<String>();
|
||||
let text = String::from(c);
|
||||
let style = text_style_to_tui_style(style);
|
||||
let span = Span::styled(text, style);
|
||||
let span = Span::styled(&text, style);
|
||||
|
||||
for row in 0..height {
|
||||
buf.set_span(x, y + row, &span, width);
|
||||
for col in 0..width {
|
||||
buf.set_span(x + col, y + row, &span, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,6 +714,12 @@ fn render_bottom_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
|
|||
buf.set_span(x, y, &span, 1);
|
||||
}
|
||||
|
||||
fn render_inner_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
|
||||
let style = nu_style_to_tui(style);
|
||||
let span = Span::styled("┼", style);
|
||||
buf.set_span(x, y, &span, 1);
|
||||
}
|
||||
|
||||
fn calculate_column_width(column: &[NuText]) -> usize {
|
||||
column
|
||||
.iter()
|
||||
|
@ -676,35 +729,28 @@ fn calculate_column_width(column: &[NuText]) -> usize {
|
|||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn render_column(
|
||||
fn render_column<T, S>(
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
available_width: u16,
|
||||
rows: &[NuText],
|
||||
) -> u16 {
|
||||
for (row, (text, style)) in rows.iter().enumerate() {
|
||||
let style = text_style_to_tui_style(*style);
|
||||
let text = strip_string(text);
|
||||
let span = Span::styled(text, style);
|
||||
rows: impl Iterator<Item = (T, S)>,
|
||||
) -> u16
|
||||
where
|
||||
T: AsRef<str>,
|
||||
S: Into<TextStyle>,
|
||||
{
|
||||
for (row, (text, style)) in rows.enumerate() {
|
||||
let style = text_style_to_tui_style(style.into());
|
||||
let span = Span::styled(text.as_ref(), style);
|
||||
buf.set_span(x, y + row as u16, &span, available_width);
|
||||
}
|
||||
|
||||
available_width
|
||||
}
|
||||
|
||||
fn strip_string(text: &str) -> String {
|
||||
String::from_utf8(strip_ansi_escapes::strip(text))
|
||||
.map_err(|_| ())
|
||||
.unwrap_or_else(|_| text.to_owned())
|
||||
}
|
||||
|
||||
fn head_row_text(head: &str, style_computer: &StyleComputer) -> NuText {
|
||||
(
|
||||
String::from(head),
|
||||
TextStyle::with_style(
|
||||
Alignment::Center,
|
||||
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown())),
|
||||
),
|
||||
)
|
||||
fn head_style(head: &str, style_computer: &StyleComputer) -> TextStyle {
|
||||
let style =
|
||||
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown()));
|
||||
TextStyle::with_style(Alignment::Center, style)
|
||||
}
|
||||
|
|
|
@ -16,21 +16,21 @@ use ratatui::{
|
|||
};
|
||||
use std::cmp::min;
|
||||
|
||||
pub struct TryView<'a> {
|
||||
pub struct TryView {
|
||||
input: Value,
|
||||
command: String,
|
||||
reactive: bool,
|
||||
table: Option<RecordView<'a>>,
|
||||
immediate: bool,
|
||||
table: Option<RecordView>,
|
||||
view_mode: bool,
|
||||
border_color: Style,
|
||||
}
|
||||
|
||||
impl<'a> TryView<'a> {
|
||||
impl TryView {
|
||||
pub fn new(input: Value) -> Self {
|
||||
Self {
|
||||
input,
|
||||
table: None,
|
||||
reactive: false,
|
||||
immediate: false,
|
||||
border_color: Style::default(),
|
||||
view_mode: false,
|
||||
command: String::new(),
|
||||
|
@ -48,7 +48,7 @@ impl<'a> TryView<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl View for TryView<'_> {
|
||||
impl View for TryView {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
let border_color = self.border_color;
|
||||
|
||||
|
@ -178,7 +178,7 @@ impl View for TryView<'_> {
|
|||
if !self.command.is_empty() {
|
||||
self.command.pop();
|
||||
|
||||
if self.reactive {
|
||||
if self.immediate {
|
||||
match self.try_run(engine_state, stack) {
|
||||
Ok(_) => info.report = Some(Report::default()),
|
||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||
|
@ -191,7 +191,7 @@ impl View for TryView<'_> {
|
|||
KeyCode::Char(c) => {
|
||||
self.command.push(*c);
|
||||
|
||||
if self.reactive {
|
||||
if self.immediate {
|
||||
match self.try_run(engine_state, stack) {
|
||||
Ok(_) => info.report = Some(Report::default()),
|
||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||
|
@ -235,7 +235,7 @@ impl View for TryView<'_> {
|
|||
|
||||
fn setup(&mut self, config: ViewConfig<'_>) {
|
||||
self.border_color = nu_style_to_tui(config.explore_config.table.separator_style);
|
||||
self.reactive = config.explore_config.try_reactive;
|
||||
self.immediate = config.explore_config.try_reactive;
|
||||
|
||||
let mut r = RecordView::new(vec![], vec![]);
|
||||
r.setup(config);
|
||||
|
@ -253,7 +253,7 @@ fn run_command(
|
|||
input: &Value,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<RecordView<'static>> {
|
||||
) -> Result<RecordView> {
|
||||
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
|
||||
|
||||
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "nu-glob"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
description = """
|
||||
|
@ -14,4 +14,4 @@ categories = ["filesystem"]
|
|||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
doc-comment = "0.3"
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -23,5 +23,5 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-path = { path="../nu-path", version = "0.94.3" }
|
||||
# serde_json = "1.0"
|
||||
# nu-path = { path="../nu-path", version = "0.95.1" }
|
||||
# serde_json = "1.0"
|
|
@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
|
|||
description = "Nushell's integrated LSP server"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
||||
name = "nu-lsp"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "../nu-cli", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-cli = { path = "../nu-cli", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
|
||||
reedline = { workspace = true }
|
||||
|
||||
|
@ -23,8 +23,8 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
|
||||
assert-json-diff = "2.0"
|
||||
assert-json-diff = "2.0"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user