Merge branch 'main' into fix-run-external-quoting
This commit is contained in:
commit
640889e8aa
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- 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
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
@ -69,7 +69,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- 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
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
||||||
|
@ -98,7 +98,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- 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
|
- name: Install Nushell
|
||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --no-default-features
|
||||||
|
@ -149,7 +149,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- 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
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
|
|
2
.github/workflows/nightly-build.yml
vendored
2
.github/workflows/nightly-build.yml
vendored
|
@ -122,7 +122,7 @@ jobs:
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- 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`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -69,7 +69,7 @@ jobs:
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- 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`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
|
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
|
@ -10,4 +10,4 @@ jobs:
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.22.1
|
uses: crate-ci/typos@v1.22.7
|
||||||
|
|
84
Cargo.lock
generated
84
Cargo.lock
generated
|
@ -920,6 +920,15 @@ dependencies = [
|
||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1678,9 +1687,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git2"
|
name = "git2"
|
||||||
version = "0.18.3"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
|
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2279,9 +2288,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -3011,7 +3020,7 @@ dependencies = [
|
||||||
"uu_mv",
|
"uu_mv",
|
||||||
"uu_uname",
|
"uu_uname",
|
||||||
"uu_whoami",
|
"uu_whoami",
|
||||||
"uucore 0.0.25",
|
"uucore",
|
||||||
"uuid",
|
"uuid",
|
||||||
"v_htmlescape",
|
"v_htmlescape",
|
||||||
"wax",
|
"wax",
|
||||||
|
@ -3020,6 +3029,17 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-derive-value"
|
||||||
|
version = "0.94.3"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.60",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-engine"
|
name = "nu-engine"
|
||||||
version = "0.94.3"
|
version = "0.94.3"
|
||||||
|
@ -3209,11 +3229,13 @@ dependencies = [
|
||||||
"byte-unit",
|
"byte-unit",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-humanize",
|
"chrono-humanize",
|
||||||
|
"convert_case",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
"miette",
|
"miette",
|
||||||
"nix",
|
"nix",
|
||||||
|
"nu-derive-value",
|
||||||
"nu-path",
|
"nu-path",
|
||||||
"nu-system",
|
"nu-system",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
|
@ -6352,93 +6374,77 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_cp"
|
name = "uu_cp"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56"
|
checksum = "c31fc5c95f7668999e129464a29e9080f69ba01ccf7a0ae43ff2cfdb15baa340"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"filetime",
|
"filetime",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"libc",
|
"libc",
|
||||||
"quick-error 2.0.1",
|
"quick-error 2.0.1",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"xattr",
|
"xattr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_mkdir"
|
name = "uu_mkdir"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae"
|
checksum = "496d95e0e3121e4d424ba62019eb84a6f1102213ca8ca16c0a2f8c652c7236c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_mktemp"
|
name = "uu_mktemp"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57"
|
checksum = "a28a0d9744bdc28ceaf13f70b959bacded91aedfd008402d72fa1e3224158653"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"rand",
|
"rand",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_mv"
|
name = "uu_mv"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022"
|
checksum = "53680908b01c5ac3cc0ee8a376de3e51a36dde2c5a5227a115a3d0977cc4539b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_uname"
|
name = "uu_uname"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5"
|
checksum = "a7f4125fb4f286313bca8f222abaefe39db54d65179ea788c91ebd3162345f4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"platform-info",
|
"platform-info",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_whoami"
|
name = "uu_whoami"
|
||||||
version = "0.0.25"
|
version = "0.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4"
|
checksum = "7f7b313901a15cfde2d88f434fcd077903d690f73cc36d1cec20f47906960aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"uucore 0.0.26",
|
"uucore",
|
||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "uucore"
|
name = "uucore"
|
||||||
version = "0.0.26"
|
version = "0.0.26"
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -39,6 +39,7 @@ members = [
|
||||||
"crates/nu-lsp",
|
"crates/nu-lsp",
|
||||||
"crates/nu-pretty-hex",
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
|
"crates/nu-derive-value",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
"crates/nu-plugin-core",
|
"crates/nu-plugin-core",
|
||||||
"crates/nu-plugin-engine",
|
"crates/nu-plugin-engine",
|
||||||
|
@ -74,6 +75,7 @@ chardetng = "0.1.17"
|
||||||
chrono = { default-features = false, version = "0.4.34" }
|
chrono = { default-features = false, version = "0.4.34" }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
|
convert_case = "0.6"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
|
@ -123,11 +125,14 @@ pathdiff = "0.2"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
print-positions = "0.6"
|
print-positions = "0.6"
|
||||||
|
proc-macro-error = { version = "1.0", default-features = false }
|
||||||
|
proc-macro2 = "1.0"
|
||||||
procfs = "0.16.0"
|
procfs = "0.16.0"
|
||||||
pwd = "1.3"
|
pwd = "1.3"
|
||||||
quick-xml = "0.31.0"
|
quick-xml = "0.31.0"
|
||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
|
@ -147,6 +152,7 @@ serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
syn = "2.0"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
tabled = { version = "0.14.0", default-features = false }
|
tabled = { version = "0.14.0", default-features = false }
|
||||||
tempfile = "3.10"
|
tempfile = "3.10"
|
||||||
|
@ -159,13 +165,13 @@ unicode-segmentation = "1.11"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
ureq = { version = "2.9", default-features = false }
|
ureq = { version = "2.9", default-features = false }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.25"
|
uu_cp = "0.0.26"
|
||||||
uu_mkdir = "0.0.25"
|
uu_mkdir = "0.0.26"
|
||||||
uu_mktemp = "0.0.25"
|
uu_mktemp = "0.0.26"
|
||||||
uu_mv = "0.0.25"
|
uu_mv = "0.0.26"
|
||||||
uu_whoami = "0.0.25"
|
uu_whoami = "0.0.26"
|
||||||
uu_uname = "0.0.25"
|
uu_uname = "0.0.26"
|
||||||
uucore = "0.0.25"
|
uucore = "0.0.26"
|
||||||
uuid = "1.8.0"
|
uuid = "1.8.0"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
|
@ -195,6 +201,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
ctrlc = { workspace = true }
|
ctrlc = { workspace = true }
|
||||||
|
dirs-next = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||||
|
@ -244,7 +251,6 @@ default = ["default-no-clipboard", "system-clipboard"]
|
||||||
# See https://github.com/nushell/nushell/pull/11535
|
# See https://github.com/nushell/nushell/pull/11535
|
||||||
default-no-clipboard = [
|
default-no-clipboard = [
|
||||||
"plugin",
|
"plugin",
|
||||||
"which-support",
|
|
||||||
"trash-support",
|
"trash-support",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
|
@ -264,7 +270,6 @@ system-clipboard = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Stable (Default)
|
# Stable (Default)
|
||||||
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
|
|
||||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||||
|
|
||||||
# SQLite commands for nushell
|
# SQLite commands for nushell
|
||||||
|
|
|
@ -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:
|
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).
|
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)
|
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
||||||
- [Dorothy](http://github.com/bevry/dorothy)
|
- [Dorothy](http://github.com/bevry/dorothy)
|
||||||
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||||
|
- [x-cmd](https://x-cmd.com/mod/nu)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -90,8 +89,7 @@ fn bench_command(
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::completions::{matches, CompletionOptions};
|
use crate::completions::{matches, CompletionOptions};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_engine::env_to_string;
|
use nu_engine::env_to_string;
|
||||||
use nu_path::home_dir;
|
use nu_path::{expand_to_real_path, home_dir};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
|
@ -185,9 +185,14 @@ pub fn complete_item(
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
let path = original_cwd.apply(p);
|
let path = original_cwd.apply(p);
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(&path, std::fs::symlink_metadata(&path).ok().as_ref())
|
lsc.style_for_path_with_metadata(
|
||||||
.map(lscolors::Style::to_nu_ansi_term_style)
|
&path,
|
||||||
.unwrap_or_default()
|
std::fs::symlink_metadata(expand_to_real_path(&path))
|
||||||
|
.ok()
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
(span, escape_path(path, want_directory), style)
|
(span, escape_path(path, want_directory), style)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,15 +8,45 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
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
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
table_mode: Option<Value>,
|
opts: EvaluateCommandsOpts,
|
||||||
no_newline: bool,
|
|
||||||
) -> Result<(), ShellError> {
|
) -> 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
|
// Translate environment variables from Strings to Values
|
||||||
convert_env_values(engine_state, stack)?;
|
convert_env_values(engine_state, stack)?;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ mod validation;
|
||||||
pub use commands::add_cli_context;
|
pub use commands::add_cli_context;
|
||||||
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||||
pub use config_files::eval_config_contents;
|
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 eval_file::evaluate_file;
|
||||||
pub use menus::NuHelpCompleter;
|
pub use menus::NuHelpCompleter;
|
||||||
pub use nu_cmd_base::util::get_init_cwd;
|
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
|
// Adds all menus to line editor
|
||||||
pub(crate) fn add_menus(
|
pub(crate) fn add_menus(
|
||||||
mut line_editor: Reedline,
|
mut line_editor: Reedline,
|
||||||
engine_state: Arc<EngineState>,
|
engine_state_ref: Arc<EngineState>,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
|
@ -83,7 +83,7 @@ pub(crate) fn add_menus(
|
||||||
line_editor = line_editor.clear_menus();
|
line_editor = line_editor.clear_menus();
|
||||||
|
|
||||||
for menu in &config.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
|
// 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),
|
("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 {
|
for (name, definition) in default_menus {
|
||||||
if !config
|
if !config
|
||||||
.menus
|
.menus
|
||||||
.iter()
|
.iter()
|
||||||
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||||
{
|
{
|
||||||
let (block, _) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
let output = parse(
|
let output = parse(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
|
@ -111,15 +114,31 @@ pub(crate) fn add_menus(
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
let mut temp_stack = Stack::new().capture();
|
let mut temp_stack = Stack::new().capture();
|
||||||
let input = PipelineData::Empty;
|
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 {
|
let new_engine_state_ref = Arc::new(engine_state);
|
||||||
for menu in create_menus(&value)? {
|
|
||||||
line_editor =
|
for res in menu_eval_results.into_iter() {
|
||||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
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,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -763,11 +763,13 @@ fn variables_completions() {
|
||||||
// Test completions for $nu
|
// Test completions for $nu
|
||||||
let suggestions = completer.complete("$nu.", 4);
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
assert_eq!(15, suggestions.len());
|
assert_eq!(17, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
|
"cache-dir".into(),
|
||||||
"config-path".into(),
|
"config-path".into(),
|
||||||
"current-exe".into(),
|
"current-exe".into(),
|
||||||
|
"data-dir".into(),
|
||||||
"default-config-dir".into(),
|
"default-config-dir".into(),
|
||||||
"env-path".into(),
|
"env-path".into(),
|
||||||
"history-enabled".into(),
|
"history-enabled".into(),
|
||||||
|
|
|
@ -194,7 +194,7 @@ pub fn eval_hook(
|
||||||
let Some(follow) = val.get("code") else {
|
let Some(follow) = val.get("code") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "code".into(),
|
col_name: "code".into(),
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: 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;
|
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> {
|
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
||||||
match range {
|
match range {
|
||||||
Range::IntRange(range) => {
|
Range::IntRange(range) => {
|
||||||
let start = range.start().try_into().unwrap_or(0);
|
let start = range.start().try_into().unwrap_or(0);
|
||||||
let end = match range.end() {
|
let end = match range.end() {
|
||||||
Bound::Included(v) => (v + 1) as isize,
|
Bound::Included(v) => v as isize,
|
||||||
Bound::Excluded(v) => v as isize,
|
Bound::Excluded(v) => (v - 1) as isize,
|
||||||
Bound::Unbounded => isize::MAX,
|
Bound::Unbounded => isize::MAX,
|
||||||
};
|
};
|
||||||
Ok((start, end))
|
Ok((start, end))
|
||||||
|
|
|
@ -32,10 +32,6 @@ serde_urlencoded = { workspace = true }
|
||||||
v_htmlescape = { workspace = true }
|
v_htmlescape = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
[features]
|
|
||||||
extra = ["default"]
|
|
||||||
default = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_expression};
|
use nu_engine::command_prelude::*;
|
||||||
use nu_parser::parse_expression;
|
|
||||||
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
|
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -57,14 +56,7 @@ impl Command for FormatPattern {
|
||||||
string_span.start + 1,
|
string_span.start + 1,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
format(
|
format(input_val, &ops, engine_state, call.head)
|
||||||
input_val,
|
|
||||||
&ops,
|
|
||||||
engine_state,
|
|
||||||
&mut working_set,
|
|
||||||
stack,
|
|
||||||
call.head,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,8 +92,6 @@ enum FormatOperation {
|
||||||
FixedText(String),
|
FixedText(String),
|
||||||
// raw input is something like {column1.column2}
|
// raw input is something like {column1.column2}
|
||||||
ValueFromColumn(String, Span),
|
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
|
/// 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.
|
/// there without any further processing.
|
||||||
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be
|
||||||
/// formatted according to the input pattern.
|
/// formatted according to the input pattern.
|
||||||
/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form:
|
|
||||||
/// "$it.column1.column2" or "$variable"
|
/// "$it.column1.column2" or "$variable"
|
||||||
fn extract_formatting_operations(
|
fn extract_formatting_operations(
|
||||||
input: String,
|
input: String,
|
||||||
|
@ -161,10 +150,17 @@ fn extract_formatting_operations(
|
||||||
|
|
||||||
if !column_name.is_empty() {
|
if !column_name.is_empty() {
|
||||||
if column_need_eval {
|
if column_need_eval {
|
||||||
output.push(FormatOperation::ValueNeedEval(
|
return Err(ShellError::GenericError {
|
||||||
column_name.clone(),
|
error: "Removed functionality".into(),
|
||||||
Span::new(span_start + column_span_start, span_start + column_span_end),
|
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 {
|
} else {
|
||||||
output.push(FormatOperation::ValueFromColumn(
|
output.push(FormatOperation::ValueFromColumn(
|
||||||
column_name.clone(),
|
column_name.clone(),
|
||||||
|
@ -185,8 +181,6 @@ fn format(
|
||||||
input_data: Value,
|
input_data: Value,
|
||||||
format_operations: &[FormatOperation],
|
format_operations: &[FormatOperation],
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
stack: &mut Stack,
|
|
||||||
head_span: Span,
|
head_span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let data_as_value = input_data;
|
let data_as_value = input_data;
|
||||||
|
@ -194,13 +188,7 @@ fn format(
|
||||||
// We can only handle a Record or a List of Records
|
// We can only handle a Record or a List of Records
|
||||||
match data_as_value {
|
match data_as_value {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
match format_record(
|
match format_record(format_operations, &data_as_value, engine_state) {
|
||||||
format_operations,
|
|
||||||
&data_as_value,
|
|
||||||
engine_state,
|
|
||||||
working_set,
|
|
||||||
stack,
|
|
||||||
) {
|
|
||||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||||
Err(value) => Err(value),
|
Err(value) => Err(value),
|
||||||
}
|
}
|
||||||
|
@ -211,13 +199,7 @@ fn format(
|
||||||
for val in vals.iter() {
|
for val in vals.iter() {
|
||||||
match val {
|
match val {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
match format_record(
|
match format_record(format_operations, val, engine_state) {
|
||||||
format_operations,
|
|
||||||
val,
|
|
||||||
engine_state,
|
|
||||||
working_set,
|
|
||||||
stack,
|
|
||||||
) {
|
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
list.push(Value::string(value, head_span));
|
list.push(Value::string(value, head_span));
|
||||||
}
|
}
|
||||||
|
@ -256,12 +238,9 @@ fn format_record(
|
||||||
format_operations: &[FormatOperation],
|
format_operations: &[FormatOperation],
|
||||||
data_as_value: &Value,
|
data_as_value: &Value,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
stack: &mut Stack,
|
|
||||||
) -> Result<String, ShellError> {
|
) -> Result<String, ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let eval_expression = get_eval_expression(engine_state);
|
|
||||||
|
|
||||||
for op in format_operations {
|
for op in format_operations {
|
||||||
match op {
|
match op {
|
||||||
|
@ -283,23 +262,6 @@ fn format_record(
|
||||||
Err(se) => return Err(se),
|
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)
|
Ok(output)
|
||||||
|
|
|
@ -25,7 +25,6 @@ shadow-rs = { version = "0.28", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mimalloc = []
|
mimalloc = []
|
||||||
which-support = []
|
|
||||||
trash-support = []
|
trash-support = []
|
||||||
sqlite = []
|
sqlite = []
|
||||||
static-link-openssl = []
|
static-link-openssl = []
|
||||||
|
|
|
@ -67,8 +67,8 @@ impl Command for Def {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define a custom wrapper for an external command",
|
description: "Define a custom wrapper for an external command",
|
||||||
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
|
||||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
result: Some(Value::test_string("spam\tspam")),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||||
use nu_protocol::engine::CommandType;
|
use nu_protocol::engine::CommandType;
|
||||||
|
use nu_protocol::ParseWarning;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct For;
|
pub struct For;
|
||||||
|
@ -30,7 +31,7 @@ impl Command for For {
|
||||||
.required("block", SyntaxShape::Block, "The block to run.")
|
.required("block", SyntaxShape::Block, "The block to run.")
|
||||||
.switch(
|
.switch(
|
||||||
"numbered",
|
"numbered",
|
||||||
"return a numbered item ($it.index and $it.item)",
|
"DEPRECATED: return a numbered item ($it.index and $it.item)",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
.creates_scope()
|
.creates_scope()
|
||||||
|
@ -78,6 +79,20 @@ impl Command for For {
|
||||||
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
||||||
|
|
||||||
let numbered = call.has_flag(engine_state, stack, "numbered")?;
|
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 ctrlc = engine_state.ctrlc.clone();
|
||||||
let engine_state = engine_state.clone();
|
let engine_state = engine_state.clone();
|
||||||
|
@ -198,8 +213,7 @@ impl Command for For {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Number each item and print a message",
|
description: "Number each item and print a message",
|
||||||
example:
|
example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
|
||||||
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
|
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -122,6 +122,10 @@ impl Command for If {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["else", "conditional"]
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
|
|
|
@ -10,7 +10,7 @@ impl Command for Try {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
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 {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
@ -18,7 +18,7 @@ impl Command for Try {
|
||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.required("try_block", SyntaxShape::Block, "Block to run.")
|
.required("try_block", SyntaxShape::Block, "Block to run.")
|
||||||
.optional(
|
.optional(
|
||||||
"catch_block",
|
"catch_closure",
|
||||||
SyntaxShape::Keyword(
|
SyntaxShape::Keyword(
|
||||||
b"catch".to_vec(),
|
b"catch".to_vec(),
|
||||||
Box::new(SyntaxShape::OneOf(vec![
|
Box::new(SyntaxShape::OneOf(vec![
|
||||||
|
@ -26,7 +26,7 @@ impl Command for Try {
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
])),
|
])),
|
||||||
),
|
),
|
||||||
"Block to run if try block fails.",
|
"Closure to run if try block fails.",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
@ -85,9 +85,14 @@ impl Command for Try {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Try to run a missing command",
|
description: "Try to run a missing command",
|
||||||
example: "try { asdfasdf } catch { 'missing' } ",
|
example: "try { asdfasdf } catch { 'missing' }",
|
||||||
result: Some(Value::test_string("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,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,11 +160,6 @@ fn features_enabled() -> Vec<String> {
|
||||||
|
|
||||||
// NOTE: There should be another way to know features on.
|
// NOTE: There should be another way to know features on.
|
||||||
|
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
{
|
|
||||||
names.push("which".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "trash-support")]
|
#[cfg(feature = "trash-support")]
|
||||||
{
|
{
|
||||||
names.push("trash".to_string());
|
names.push("trash".to_string());
|
||||||
|
|
|
@ -134,7 +134,6 @@ workspace = true
|
||||||
plugin = ["nu-parser/plugin"]
|
plugin = ["nu-parser/plugin"]
|
||||||
sqlite = ["rusqlite"]
|
sqlite = ["rusqlite"]
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||||
|
|
|
@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
let range = &args.indexes;
|
let range = &args.indexes;
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
use std::cmp::{self, Ordering};
|
|
||||||
let len = val.len() as isize;
|
let len = val.len() as isize;
|
||||||
|
|
||||||
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
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 {
|
if start > end {
|
||||||
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 {
|
|
||||||
Value::binary(vec![], head)
|
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(),
|
Value::Error { .. } => input.clone(),
|
||||||
|
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
|
|
|
@ -194,7 +194,7 @@ fn run_histogram(
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: head_span,
|
span: Some(head_span),
|
||||||
src_span: list_span,
|
src_span: list_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ fn record_to_path_member(
|
||||||
let Some(value) = record.get("value") else {
|
let Some(value) = record.get("value") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "value".into(),
|
col_name: "value".into(),
|
||||||
span: val_span,
|
span: Some(val_span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
Value::Filesize { .. } => input.clone(),
|
Value::Filesize { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::filesize(*val, value_span),
|
Value::Int { val, .. } => Value::filesize(*val, value_span),
|
||||||
Value::Float { val, .. } => Value::filesize(*val as i64, 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),
|
Ok(val) => Value::filesize(val, value_span),
|
||||||
Err(error) => Value::error(error, 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
|
// Get the Locale so we know what the thousands separator is
|
||||||
let locale = get_system_locale();
|
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
|
// Hadle negative file size
|
||||||
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
|
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
|
||||||
match stripped_negative_string.parse::<bytesize::ByteSize>() {
|
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)),
|
Err(_) => Err(string_convert_error(span)),
|
||||||
}
|
}
|
||||||
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
|
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
|
||||||
match stripped_positive_string.parse::<bytesize::ByteSize>() {
|
match stripped_positive_string.parse::<bytesize::ByteSize>() {
|
||||||
Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => {
|
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)),
|
_ => Err(string_convert_error(span)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match clean_string.parse::<bytesize::ByteSize>() {
|
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)),
|
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 {
|
fn string_convert_error(span: Span) -> ShellError {
|
||||||
ShellError::CantConvert {
|
ShellError::CantConvert {
|
||||||
to_type: "filesize".into(),
|
to_type: "filesize".into(),
|
||||||
|
|
|
@ -127,7 +127,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
SysTemp,
|
SysTemp,
|
||||||
SysUsers,
|
SysUsers,
|
||||||
UName,
|
UName,
|
||||||
|
Which,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
|
@ -172,9 +172,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
))]
|
))]
|
||||||
bind_command! { Ps };
|
bind_command! { Ps };
|
||||||
|
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
bind_command! { Which };
|
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Char,
|
Char,
|
||||||
|
|
|
@ -35,12 +35,12 @@ impl Command for Touch {
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"modified",
|
"modified",
|
||||||
"change the modification time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
|
"change the modification time of the file or directory. If no reference file/directory is given, the current time is used",
|
||||||
Some('m'),
|
Some('m'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"access",
|
"access",
|
||||||
"change the access time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
|
"change the access time of the file or directory. If no reference file/directory is given, the current time is used",
|
||||||
Some('a'),
|
Some('a'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
|
@ -189,11 +189,6 @@ impl Command for Touch {
|
||||||
example: r#"touch -m -r fixture.json d e"#,
|
example: r#"touch -m -r fixture.json d e"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: r#"Changes the last accessed time of "fixture.json" to a date"#,
|
|
||||||
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ pub fn split(
|
||||||
Some(group_key) => Ok(group_key.coerce_string()?),
|
Some(group_key) => Ok(group_key.coerce_string()?),
|
||||||
None => Err(ShellError::CantFindColumn {
|
None => Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.item.to_string(),
|
col_name: column_name.item.to_string(),
|
||||||
span: column_name.span,
|
span: Some(column_name.span),
|
||||||
src_span: row.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()) {
|
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: nonexistent,
|
col_name: nonexistent,
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use chrono::SecondsFormat;
|
use chrono::{DateTime, Datelike, FixedOffset, Timelike};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
|
@ -49,9 +49,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
|
||||||
Value::Int { val, .. } => toml::Value::Integer(*val),
|
Value::Int { val, .. } => toml::Value::Integer(*val),
|
||||||
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
Value::Filesize { val, .. } => toml::Value::Integer(*val),
|
||||||
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
|
||||||
toml::Value::String(val.to_rfc3339_opts(SecondsFormat::AutoSi, false))
|
|
||||||
}
|
|
||||||
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
||||||
Value::Float { val, .. } => toml::Value::Float(*val),
|
Value::Float { val, .. } => toml::Value::Float(*val),
|
||||||
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
|
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
|
||||||
|
@ -157,6 +155,43 @@ fn to_toml(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert chrono datetime into a toml::Value datetime. The latter uses its
|
||||||
|
/// own ad-hoc datetime types, which makes this somewhat convoluted.
|
||||||
|
fn to_toml_datetime(datetime: &DateTime<FixedOffset>) -> toml::value::Datetime {
|
||||||
|
let date = toml::value::Date {
|
||||||
|
// TODO: figure out what to do with BC dates, because the toml
|
||||||
|
// crate doesn't support them. Same for large years, which
|
||||||
|
// don't fit in u16.
|
||||||
|
year: datetime.year_ce().1 as u16,
|
||||||
|
// Panic: this is safe, because chrono guarantees that the month
|
||||||
|
// value will be between 1 and 12 and the day will be between 1
|
||||||
|
// and 31
|
||||||
|
month: datetime.month() as u8,
|
||||||
|
day: datetime.day() as u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = toml::value::Time {
|
||||||
|
// Panic: same as before, chorono guarantees that all of the following 3
|
||||||
|
// methods return values less than 65'000
|
||||||
|
hour: datetime.hour() as u8,
|
||||||
|
minute: datetime.minute() as u8,
|
||||||
|
second: datetime.second() as u8,
|
||||||
|
nanosecond: datetime.nanosecond(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = toml::value::Offset::Custom {
|
||||||
|
// Panic: minute timezone offset fits into i16 (that's more than
|
||||||
|
// 1000 hours)
|
||||||
|
minutes: (-datetime.timezone().utc_minus_local() / 60) as i16,
|
||||||
|
};
|
||||||
|
|
||||||
|
toml::value::Datetime {
|
||||||
|
date: Some(date),
|
||||||
|
time: Some(time),
|
||||||
|
offset: Some(offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -181,7 +216,20 @@ mod tests {
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let reference_date = toml::Value::String(String::from("1980-10-12T10:12:44+02:00"));
|
let reference_date = toml::Value::Datetime(toml::value::Datetime {
|
||||||
|
date: Some(toml::value::Date {
|
||||||
|
year: 1980,
|
||||||
|
month: 10,
|
||||||
|
day: 12,
|
||||||
|
}),
|
||||||
|
time: Some(toml::value::Time {
|
||||||
|
hour: 10,
|
||||||
|
minute: 12,
|
||||||
|
second: 44,
|
||||||
|
nanosecond: 0,
|
||||||
|
}),
|
||||||
|
offset: Some(toml::value::Offset::Custom { minutes: 120 }),
|
||||||
|
});
|
||||||
|
|
||||||
let result = helper(&engine_state, &test_date);
|
let result = helper(&engine_state, &test_date);
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||||
let usage = sig.usage;
|
let usage = sig.usage;
|
||||||
let search_terms = sig.search_terms;
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
let command_type = format!("{:?}", decl.command_type()).to_ascii_lowercase();
|
let command_type = decl.command_type().to_string();
|
||||||
|
|
||||||
// Build table of parameters
|
// Build table of parameters
|
||||||
let param_table = {
|
let param_table = {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use super::PathSubcommandArguments;
|
use super::PathSubcommandArguments;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use std::path::{Path, PathBuf};
|
use std::{
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
pwd: PathBuf,
|
pwd: PathBuf,
|
||||||
|
@ -36,7 +39,7 @@ impl Command for SubCommand {
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This checks the file system to confirm the path's object type.
|
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 {
|
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 {
|
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||||
let path = nu_path::expand_path_with(path, &args.pwd, true);
|
let path = nu_path::expand_path_with(path, &args.pwd, true);
|
||||||
let meta = path.symlink_metadata();
|
match path.symlink_metadata() {
|
||||||
let ty = meta.as_ref().map(get_file_type).unwrap_or("");
|
Ok(metadata) => Value::string(get_file_type(&metadata), span),
|
||||||
Value::string(ty, 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 {
|
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()) {
|
if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: nonexistent,
|
col_name: nonexistent,
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ struct Arguments {
|
||||||
substring: String,
|
substring: String,
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
case_insensitive: bool,
|
case_insensitive: bool,
|
||||||
not_contain: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdArgument for Arguments {
|
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.",
|
"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("ignore-case", "search is case insensitive", Some('i'))
|
||||||
.switch("not", "DEPRECATED OPTION: does not contain", Some('n'))
|
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,27 +61,12 @@ impl Command for SubCommand {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> 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: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let args = Arguments {
|
let args = Arguments {
|
||||||
substring: call.req::<String>(engine_state, stack, 0)?,
|
substring: call.req::<String>(engine_state, stack, 0)?,
|
||||||
cell_paths,
|
cell_paths,
|
||||||
case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?,
|
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())
|
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)?,
|
substring: call.req_const::<String>(working_set, 0)?,
|
||||||
cell_paths,
|
cell_paths,
|
||||||
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
|
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
|
||||||
not_contain: call.has_flag_const(working_set, "not")?,
|
|
||||||
};
|
};
|
||||||
operate(
|
operate(
|
||||||
action,
|
action,
|
||||||
|
@ -183,7 +165,6 @@ fn action(
|
||||||
input: &Value,
|
input: &Value,
|
||||||
Arguments {
|
Arguments {
|
||||||
case_insensitive,
|
case_insensitive,
|
||||||
not_contain,
|
|
||||||
substring,
|
substring,
|
||||||
..
|
..
|
||||||
}: &Arguments,
|
}: &Arguments,
|
||||||
|
@ -191,23 +172,11 @@ fn action(
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::String { val, .. } => Value::bool(
|
Value::String { val, .. } => Value::bool(
|
||||||
match case_insensitive {
|
if *case_insensitive {
|
||||||
true => {
|
val.to_folded_case()
|
||||||
if *not_contain {
|
.contains(substring.to_folded_case().as_str())
|
||||||
!val.to_folded_case()
|
} else {
|
||||||
.contains(substring.to_folded_case().as_str())
|
val.contains(substring)
|
||||||
} else {
|
|
||||||
val.to_folded_case()
|
|
||||||
.contains(substring.to_folded_case().as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
if *not_contain {
|
|
||||||
!val.contains(substring)
|
|
||||||
} else {
|
|
||||||
val.contains(substring)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,6 @@ use nu_cmd_base::{
|
||||||
};
|
};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{engine::StateWorkingSet, Range};
|
use nu_protocol::{engine::StateWorkingSet, Range};
|
||||||
use std::cmp::Ordering;
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -151,6 +150,11 @@ impl Command for SubCommand {
|
||||||
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
||||||
result: Some(Value::test_string("ふが")),
|
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
|
options.0
|
||||||
};
|
};
|
||||||
let end: isize = if options.1 < 0 {
|
let end: isize = if options.1 < 0 {
|
||||||
std::cmp::max(len + options.1, 0)
|
options.1 + len
|
||||||
} else {
|
} else {
|
||||||
options.1
|
options.1
|
||||||
};
|
};
|
||||||
|
|
||||||
if start < len && end >= 0 {
|
if start > end {
|
||||||
match start.cmp(&end) {
|
Value::string("", head)
|
||||||
Ordering::Equal => Value::string("", head),
|
} else {
|
||||||
Ordering::Greater => Value::error(
|
Value::string(
|
||||||
ShellError::TypeMismatch {
|
{
|
||||||
err_message: "End must be greater than or equal to Start".to_string(),
|
if end == isize::MAX {
|
||||||
span: head,
|
if args.graphemes {
|
||||||
},
|
|
||||||
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 {
|
|
||||||
s.graphemes(true)
|
s.graphemes(true)
|
||||||
.skip(start as usize)
|
.skip(start as usize)
|
||||||
.take((end - start) as usize)
|
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join("")
|
.join("")
|
||||||
} else {
|
} else {
|
||||||
String::from_utf8_lossy(
|
String::from_utf8_lossy(
|
||||||
&s.bytes()
|
&s.bytes().skip(start as usize).collect::<Vec<_>>(),
|
||||||
.skip(start as usize)
|
|
||||||
.take((end - start) as usize)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
)
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
},
|
} else if args.graphemes {
|
||||||
head,
|
s.graphemes(true)
|
||||||
),
|
.skip(start as usize)
|
||||||
}
|
.take((end - start + 1) as usize)
|
||||||
} else {
|
.collect::<Vec<&str>>()
|
||||||
Value::string("", head)
|
.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.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
@ -243,6 +237,7 @@ mod tests {
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(SubCommand {})
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
struct Expectation<'a> {
|
struct Expectation<'a> {
|
||||||
options: (isize, isize),
|
options: (isize, isize),
|
||||||
expected: &'a str,
|
expected: &'a str,
|
||||||
|
@ -266,18 +261,19 @@ mod tests {
|
||||||
let word = Value::test_string("andres");
|
let word = Value::test_string("andres");
|
||||||
|
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
expectation("a", (0, 1)),
|
expectation("a", (0, 0)),
|
||||||
expectation("an", (0, 2)),
|
expectation("an", (0, 1)),
|
||||||
expectation("and", (0, 3)),
|
expectation("and", (0, 2)),
|
||||||
expectation("andr", (0, 4)),
|
expectation("andr", (0, 3)),
|
||||||
expectation("andre", (0, 5)),
|
expectation("andre", (0, 4)),
|
||||||
|
expectation("andres", (0, 5)),
|
||||||
expectation("andres", (0, 6)),
|
expectation("andres", (0, 6)),
|
||||||
expectation("", (0, -6)),
|
expectation("a", (0, -6)),
|
||||||
expectation("a", (0, -5)),
|
expectation("an", (0, -5)),
|
||||||
expectation("an", (0, -4)),
|
expectation("and", (0, -4)),
|
||||||
expectation("and", (0, -3)),
|
expectation("andr", (0, -3)),
|
||||||
expectation("andr", (0, -2)),
|
expectation("andre", (0, -2)),
|
||||||
expectation("andre", (0, -1)),
|
expectation("andres", (0, -1)),
|
||||||
// str substring [ -4 , _ ]
|
// str substring [ -4 , _ ]
|
||||||
// str substring -4 ,
|
// str substring -4 ,
|
||||||
expectation("dres", (-4, isize::MAX)),
|
expectation("dres", (-4, isize::MAX)),
|
||||||
|
@ -292,6 +288,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for expectation in &cases {
|
for expectation in &cases {
|
||||||
|
println!("{:?}", expectation);
|
||||||
let expected = expectation.expected;
|
let expected = expectation.expected;
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
use super::trim_cstyle_null;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysCpu;
|
pub struct SysCpu;
|
||||||
|
@ -26,7 +28,7 @@ impl Command for SysCpu {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::cpu(call.head).into_pipeline_data())
|
Ok(cpu(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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 nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::Disks;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysDisks;
|
pub struct SysDisks;
|
||||||
|
@ -26,7 +28,7 @@ impl Command for SysDisks {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::disks(call.head).into_pipeline_data())
|
Ok(disks(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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 nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::System;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysHost;
|
pub struct SysHost;
|
||||||
|
@ -26,8 +29,7 @@ impl Command for SysHost {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let host = super::host(call.head);
|
Ok(host(call.head).into_pipeline_data())
|
||||||
Ok(Value::record(host, call.head).into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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 nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::System;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysMem;
|
pub struct SysMem;
|
||||||
|
@ -26,7 +27,7 @@ impl Command for SysMem {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::mem(call.head).into_pipeline_data())
|
Ok(mem(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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 temp::SysTemp;
|
||||||
pub use users::SysUsers;
|
pub use users::SysUsers;
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset, Local};
|
fn trim_cstyle_null(s: impl AsRef<str>) -> String {
|
||||||
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 {
|
|
||||||
s.as_ref().trim_matches('\0').into()
|
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 nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::Networks;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysNet;
|
pub struct SysNet;
|
||||||
|
@ -26,7 +28,7 @@ impl Command for SysNet {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::net(call.head).into_pipeline_data())
|
Ok(net(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
|
@ -20,41 +20,17 @@ impl Command for Sys {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
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(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
nu_protocol::report_error_new(
|
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::Components;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysTemp;
|
pub struct SysTemp;
|
||||||
|
@ -30,7 +31,7 @@ impl Command for SysTemp {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::temp(call.head).into_pipeline_data())
|
Ok(temp(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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 nu_engine::command_prelude::*;
|
||||||
|
use sysinfo::Users;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SysUsers;
|
pub struct SysUsers;
|
||||||
|
@ -11,7 +13,7 @@ impl Command for SysUsers {
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("sys users")
|
Signature::build("sys users")
|
||||||
.category(Category::System)
|
.category(Category::System)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::record())])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -25,7 +27,7 @@ impl Command for SysUsers {
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(super::users(call.head).into_pipeline_data())
|
Ok(users(call.head).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
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
|
all_entries
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
fn get_first_entry_in_path(
|
fn get_first_entry_in_path(
|
||||||
item: &str,
|
item: &str,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -104,17 +103,6 @@ fn get_first_entry_in_path(
|
||||||
.ok()
|
.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(
|
fn get_all_entries_in_path(
|
||||||
item: &str,
|
item: &str,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -129,16 +117,6 @@ fn get_all_entries_in_path(
|
||||||
.unwrap_or_default()
|
.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)]
|
#[derive(Debug)]
|
||||||
struct WhichArgs {
|
struct WhichArgs {
|
||||||
applications: Vec<Spanned<String>>,
|
applications: Vec<Spanned<String>>,
|
||||||
|
|
|
@ -170,7 +170,10 @@ impl Command for Table {
|
||||||
}),
|
}),
|
||||||
Value::test_record(record! {
|
Value::test_record(record! {
|
||||||
"a" => Value::test_int(3),
|
"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! {
|
Value::test_record(record! {
|
||||||
"a" => Value::test_int(3),
|
"a" => Value::test_int(3),
|
||||||
"b" => Value::test_int(4),
|
"b" => Value::test_list(vec![
|
||||||
|
Value::test_int(4),
|
||||||
|
Value::test_int(4),
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||||
"[[c1,c3,c4,c5]; ['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 c5(char nl)a b c d e\"",
|
||||||
"[[c1,c2,c3,c4]; [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\"",
|
"$\"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)
|
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)\"";
|
-rw-r--r-- 1 root root 3.0K Mar 20 07:23 ~asdf(char nl)\"";
|
||||||
let expected = "[
|
let expected = "[
|
||||||
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column8'];
|
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column7', 'column8'];
|
||||||
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20 08:28', '='],
|
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20', '08:28', '='],
|
||||||
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20 08:18', '~'],
|
['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']
|
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20', '07:23', '~asdf']
|
||||||
]";
|
]";
|
||||||
let range = "5..6";
|
let range = "5..6";
|
||||||
let cmd = format!(
|
let cmd = format!(
|
||||||
|
|
|
@ -4,8 +4,9 @@ use nu_test_support::nu;
|
||||||
fn error_label_works() {
|
fn error_label_works() {
|
||||||
let actual = nu!("error make {msg:foo label:{text:unseen}}");
|
let actual = nu!("error make {msg:foo label:{text:unseen}}");
|
||||||
|
|
||||||
assert!(actual.err.contains("unseen"));
|
assert!(actual
|
||||||
assert!(actual.err.contains("╰──"));
|
.err
|
||||||
|
.contains("label at line 1, columns 1 to 10: unseen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -37,7 +37,7 @@ fn given_fields_can_be_column_paths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_use_variables() {
|
fn cant_use_variables() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
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]
|
#[test]
|
||||||
|
@ -55,7 +56,7 @@ fn error_unmatched_brace() {
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
open cargo_sample.toml
|
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"));
|
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]
|
#[test]
|
||||||
fn into_filesize_negative_str() {
|
fn into_filesize_negative_str() {
|
||||||
let actual = nu!("'-1' | into filesize");
|
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"));
|
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]
|
#[test]
|
||||||
fn into_filesize_positive_str() {
|
fn into_filesize_positive_str() {
|
||||||
let actual = nu!("'+1' | into filesize");
|
let actual = nu!("'+1' | into filesize");
|
||||||
|
|
|
@ -125,7 +125,6 @@ mod upsert;
|
||||||
mod url;
|
mod url;
|
||||||
mod use_;
|
mod use_;
|
||||||
mod where_;
|
mod where_;
|
||||||
#[cfg(feature = "which-support")]
|
|
||||||
mod which;
|
mod which;
|
||||||
mod while_;
|
mod while_;
|
||||||
mod with_env;
|
mod with_env;
|
||||||
|
|
|
@ -255,7 +255,7 @@ fn substrings_the_input() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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| {
|
Playground::setup("str_test_9", |dirs, sandbox| {
|
||||||
sandbox.with_files(&[FileWithContent(
|
sandbox.with_files(&[FileWithContent(
|
||||||
"sample.toml",
|
"sample.toml",
|
||||||
|
@ -270,12 +270,10 @@ fn substring_errors_if_start_index_is_greater_than_end_index() {
|
||||||
r#"
|
r#"
|
||||||
open sample.toml
|
open sample.toml
|
||||||
| str substring 6..4 fortune.teller.phone
|
| str substring 6..4 fortune.teller.phone
|
||||||
|
| get fortune.teller.phone
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
assert_eq!(actual.out, "")
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("End must be greater than or equal to Start"))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn str_reverse() {
|
fn str_reverse() {
|
||||||
let actual = nu!(r#"
|
let actual = nu!(r#"
|
||||||
|
|
|
@ -906,7 +906,7 @@ fn test_cp_debug_default() {
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
if !actual
|
if !actual
|
||||||
.out
|
.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));
|
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.94.3"
|
||||||
|
|
||||||
|
[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"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
539
crates/nu-derive-value/src/from.rs
Normal file
539
crates/nu-derive-value/src/from.rs
Normal file
|
@ -0,0 +1,539 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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: <Option<String> as nu_protocol::FromValue>::from_value(
|
||||||
|
/// record
|
||||||
|
/// .remove("favorite_toy")
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string("favorite_toy"),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?,
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 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| {
|
||||||
|
// TODO: handle missing fields for Options as None
|
||||||
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
|
let ident_s = ident.to_string();
|
||||||
|
let ty = &field.ty;
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
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
|
||||||
|
);
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression, RecordItem},
|
ast::{Argument, Call, Expr, Expression, RecordItem},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID},
|
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,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, fmt::Write};
|
use std::{collections::HashMap, fmt::Write};
|
||||||
|
@ -296,6 +296,28 @@ fn get_documentation(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = &example.result {
|
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
|
let table = engine_state
|
||||||
.find_decl("table".as_bytes(), &[])
|
.find_decl("table".as_bytes(), &[])
|
||||||
.and_then(|decl_id| {
|
.and_then(|decl_id| {
|
||||||
|
@ -304,7 +326,7 @@ fn get_documentation(
|
||||||
.run(
|
.run(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&Call::new(Span::new(0, 0)),
|
&table_call,
|
||||||
PipelineData::Value(result.clone(), None),
|
PipelineData::Value(result.clone(), None),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
@ -208,11 +208,13 @@ fn eval_external(
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let decl_id = engine_state
|
let decl_id = engine_state
|
||||||
.find_decl("run-external".as_bytes(), &[])
|
.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 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());
|
call.add_positional(head.clone());
|
||||||
|
|
||||||
|
@ -712,7 +714,7 @@ impl Eval for EvalRuntime {
|
||||||
args: &[ExternalArgument],
|
args: &[ExternalArgument],
|
||||||
_: Span,
|
_: Span,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let span = head.span;
|
let span = head.span(&engine_state);
|
||||||
// FIXME: protect this collect with ctrl-c
|
// FIXME: protect this collect with ctrl-c
|
||||||
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
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);
|
let var_info = engine_state.get_var(*var_id);
|
||||||
if var_info.mutable {
|
if var_info.mutable {
|
||||||
stack.add_var(*var_id, rhs);
|
stack.add_var(*var_id, rhs);
|
||||||
Ok(Value::nothing(lhs.span))
|
Ok(Value::nothing(lhs.span(&engine_state)))
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
Err(ShellError::AssignmentRequiresMutableVar {
|
||||||
|
lhs_span: lhs.span(&engine_state),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::FullCellPath(cell_path) => {
|
Expr::FullCellPath(cell_path) => {
|
||||||
|
@ -797,7 +801,7 @@ impl Eval for EvalRuntime {
|
||||||
// Reject attempts to assign to the entire $env
|
// Reject attempts to assign to the entire $env
|
||||||
if cell_path.tail.is_empty() {
|
if cell_path.tail.is_empty() {
|
||||||
return Err(ShellError::CannotReplaceEnv {
|
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)?;
|
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
|
||||||
stack.add_var(*var_id, lhs);
|
stack.add_var(*var_id, lhs);
|
||||||
}
|
}
|
||||||
Ok(Value::nothing(cell_path.head.span))
|
Ok(Value::nothing(cell_path.head.span(&engine_state)))
|
||||||
} else {
|
} 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))
|
Ok(Value::string(name, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
fn unreachable(engine_state: &EngineState, expr: &Expression) -> Result<Value, ShellError> {
|
||||||
Ok(Value::nothing(expr.span))
|
Ok(Value::nothing(expr.span(&engine_state)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl NuCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewCommand for NuCmd {
|
impl ViewCommand for NuCmd {
|
||||||
type View = NuView<'static>;
|
type View = NuView;
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
Self::NAME
|
Self::NAME
|
||||||
|
@ -72,12 +72,12 @@ impl ViewCommand for NuCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum NuView<'a> {
|
pub enum NuView {
|
||||||
Records(RecordView<'a>),
|
Records(RecordView),
|
||||||
Preview(Preview),
|
Preview(Preview),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for NuView<'_> {
|
impl View for NuView {
|
||||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||||
match self {
|
match self {
|
||||||
NuView::Records(v) => v.draw(f, area, cfg, layout),
|
NuView::Records(v) => v.draw(f, area, cfg, layout),
|
||||||
|
|
|
@ -30,7 +30,7 @@ impl TableCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewCommand for TableCmd {
|
impl ViewCommand for TableCmd {
|
||||||
type View = RecordView<'static>;
|
type View = RecordView;
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
Self::NAME
|
Self::NAME
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl TryCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewCommand for TryCmd {
|
impl ViewCommand for TryCmd {
|
||||||
type View = TryView<'static>;
|
type View = TryView;
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
Self::NAME
|
Self::NAME
|
||||||
|
|
|
@ -23,23 +23,20 @@ use nu_protocol::{
|
||||||
Config, Record, Span, Value,
|
Config, Record, Span, Value,
|
||||||
};
|
};
|
||||||
use ratatui::{layout::Rect, widgets::Block};
|
use ratatui::{layout::Rect, widgets::Block};
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub use self::table_widget::Orientation;
|
pub use self::table_widget::Orientation;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RecordView<'a> {
|
pub struct RecordView {
|
||||||
layer_stack: Vec<RecordLayer<'a>>,
|
layer_stack: Vec<RecordLayer>,
|
||||||
mode: UIMode,
|
mode: UIMode,
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
cfg: ExploreConfig,
|
cfg: ExploreConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RecordView<'a> {
|
impl RecordView {
|
||||||
pub fn new(
|
pub fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
|
||||||
columns: impl Into<Cow<'a, [String]>>,
|
|
||||||
records: impl Into<Cow<'a, [Vec<Value>]>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
layer_stack: vec![RecordLayer::new(columns, records)],
|
layer_stack: vec![RecordLayer::new(columns, records)],
|
||||||
mode: UIMode::View,
|
mode: UIMode::View,
|
||||||
|
@ -64,13 +61,13 @@ impl<'a> RecordView<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: rename to get_layer
|
// todo: rename to get_layer
|
||||||
pub fn get_layer_last(&self) -> &RecordLayer<'a> {
|
pub fn get_layer_last(&self) -> &RecordLayer {
|
||||||
self.layer_stack
|
self.layer_stack
|
||||||
.last()
|
.last()
|
||||||
.expect("we guarantee that 1 entry is always in a list")
|
.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
|
self.layer_stack
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.expect("we guarantee that 1 entry is always in a list")
|
.expect("we guarantee that 1 entry is always in a list")
|
||||||
|
@ -134,32 +131,39 @@ impl<'a> RecordView<'a> {
|
||||||
Orientation::Left => (column, row),
|
Orientation::Left => (column, row),
|
||||||
};
|
};
|
||||||
|
|
||||||
if layer.records.len() > row && layer.records[row].len() > column {
|
if row >= layer.count_rows() || column >= layer.count_columns() {
|
||||||
layer.records[row][column].clone()
|
// actually must never happen; unless cursor works incorrectly
|
||||||
} else {
|
// if being sure about cursor it can be deleted;
|
||||||
Value::nothing(Span::unknown())
|
return Value::nothing(Span::unknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layer.record_values[row][column].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a table widget.
|
fn create_table_widget<'a>(&'a mut self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||||
/// WARNING: this is currently really slow on large data sets.
|
let style = self.cfg.table;
|
||||||
/// 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();
|
|
||||||
let style_computer = cfg.style_computer;
|
let style_computer = cfg.style_computer;
|
||||||
let Position { row, column } = self.get_window_origin();
|
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(
|
TableWidget::new(
|
||||||
headers,
|
headers,
|
||||||
data,
|
data,
|
||||||
style_computer,
|
style_computer,
|
||||||
row,
|
row,
|
||||||
column,
|
column,
|
||||||
self.cfg.table,
|
style,
|
||||||
layer.orientation,
|
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) {
|
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||||
let mut table_layout = TableWidgetState::default();
|
let mut table_layout = TableWidgetState::default();
|
||||||
// TODO: creating the table widget is O(N) where N is the number of cells in the grid.
|
// 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 style_computer = StyleComputer::new(&dummy_engine_state, &dummy_stack, HashMap::new());
|
||||||
|
|
||||||
let data = convert_records_to_string(
|
let data = convert_records_to_string(
|
||||||
&self.get_layer_last().records,
|
&self.get_layer_last().record_values,
|
||||||
&nu_protocol::Config::default(),
|
&nu_protocol::Config::default(),
|
||||||
&style_computer,
|
&style_computer,
|
||||||
);
|
);
|
||||||
|
@ -274,7 +278,7 @@ impl View for RecordView<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_data(&mut self, pos: usize) -> bool {
|
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;
|
let mut i = 0;
|
||||||
for (row, cells) in data.iter().enumerate() {
|
for (row, cells) in data.iter().enumerate() {
|
||||||
|
@ -332,30 +336,35 @@ enum UIMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RecordLayer<'a> {
|
pub struct RecordLayer {
|
||||||
columns: Cow<'a, [String]>,
|
column_names: Vec<String>,
|
||||||
records: Cow<'a, [Vec<Value>]>,
|
// 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,
|
orientation: Orientation,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
was_transposed: bool,
|
was_transposed: bool,
|
||||||
cursor: WindowCursor2D,
|
cursor: WindowCursor2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RecordLayer<'a> {
|
impl RecordLayer {
|
||||||
fn new(
|
fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
|
||||||
columns: impl Into<Cow<'a, [String]>>,
|
|
||||||
records: impl Into<Cow<'a, [Vec<Value>]>>,
|
|
||||||
) -> Self {
|
|
||||||
let columns = columns.into();
|
|
||||||
let records = records.into();
|
|
||||||
|
|
||||||
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
||||||
let cursor =
|
let cursor =
|
||||||
WindowCursor2D::new(records.len(), columns.len()).expect("Failed to create cursor");
|
WindowCursor2D::new(records.len(), columns.len()).expect("Failed to create cursor");
|
||||||
|
|
||||||
|
let column_names = columns.iter().map(|s| strip_string(s)).collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
columns,
|
column_names,
|
||||||
records,
|
record_values: records,
|
||||||
|
record_text: None,
|
||||||
cursor,
|
cursor,
|
||||||
orientation: Orientation::Top,
|
orientation: Orientation::Top,
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -369,21 +378,21 @@ impl<'a> RecordLayer<'a> {
|
||||||
|
|
||||||
fn count_rows(&self) -> usize {
|
fn count_rows(&self) -> usize {
|
||||||
match self.orientation {
|
match self.orientation {
|
||||||
Orientation::Top => self.records.len(),
|
Orientation::Top => self.record_values.len(),
|
||||||
Orientation::Left => self.columns.len(),
|
Orientation::Left => self.column_names.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_columns(&self) -> usize {
|
fn count_columns(&self) -> usize {
|
||||||
match self.orientation {
|
match self.orientation {
|
||||||
Orientation::Top => self.columns.len(),
|
Orientation::Top => self.column_names.len(),
|
||||||
Orientation::Left => self.records.len(),
|
Orientation::Left => self.record_values.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_column_header(&self) -> Option<String> {
|
fn get_column_header(&self) -> Option<String> {
|
||||||
let col = self.cursor.column();
|
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) {
|
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)?;
|
let (columns, values) = collect_input(value)?;
|
||||||
|
|
||||||
Ok(RecordLayer::new(columns, values))
|
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 layer = view.get_layer_last();
|
||||||
let header = layer.get_column_header();
|
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
|
/// 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 layer = state.get_layer_last_mut();
|
||||||
let count_rows = layer.records.len();
|
let count_rows = layer.count_rows();
|
||||||
if count_rows > page_size {
|
if count_rows > page_size {
|
||||||
layer
|
layer
|
||||||
.cursor
|
.cursor
|
||||||
|
@ -620,6 +629,7 @@ fn convert_records_to_string(
|
||||||
row.iter()
|
row.iter()
|
||||||
.map(|value| {
|
.map(|value| {
|
||||||
let text = value.clone().to_abbreviated_string(cfg);
|
let text = value.clone().to_abbreviated_string(cfg);
|
||||||
|
let text = strip_string(&text);
|
||||||
let float_precision = cfg.float_precision as usize;
|
let float_precision = cfg.float_precision as usize;
|
||||||
|
|
||||||
make_styled_string(style_computer, text, Some(value), float_precision)
|
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 {
|
fn build_table_as_list(v: &RecordView) -> Value {
|
||||||
let layer = v.get_layer_last();
|
let layer = v.get_layer_last();
|
||||||
|
|
||||||
let cols = &layer.columns;
|
|
||||||
let vals = layer
|
let vals = layer
|
||||||
.records
|
.record_values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|vals| {
|
.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())
|
Value::record(record, NuSpan::unknown())
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -665,16 +679,15 @@ fn build_table_as_list(v: &RecordView) -> Value {
|
||||||
fn build_table_as_record(v: &RecordView) -> Value {
|
fn build_table_as_record(v: &RecordView) -> Value {
|
||||||
let layer = v.get_layer_last();
|
let layer = v.get_layer_last();
|
||||||
|
|
||||||
let record = if let Some(row) = layer.records.first() {
|
let mut record = Record::new();
|
||||||
layer
|
if let Some(row) = layer.record_values.first() {
|
||||||
.columns
|
record = layer
|
||||||
|
.column_names
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.zip(row.iter().cloned())
|
.zip(row.iter().cloned())
|
||||||
.collect()
|
.collect();
|
||||||
} else {
|
}
|
||||||
Record::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::record(record, NuSpan::unknown())
|
Value::record(record, NuSpan::unknown())
|
||||||
}
|
}
|
||||||
|
@ -708,17 +721,12 @@ fn get_percentage(value: usize, max: usize) -> usize {
|
||||||
((value as f32 / max as f32) * 100.0).floor() as usize
|
((value as f32 / max as f32) * 100.0).floor() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpose_table(layer: &mut RecordLayer<'_>) {
|
fn transpose_table(layer: &mut RecordLayer) {
|
||||||
let count_rows = layer.records.len();
|
let count_rows = layer.record_values.len();
|
||||||
let count_columns = layer.columns.len();
|
let count_columns = layer.column_names.len();
|
||||||
|
|
||||||
if layer.was_transposed {
|
if layer.was_transposed {
|
||||||
let data = match &mut layer.records {
|
let headers = pop_first_column(&mut layer.record_values);
|
||||||
Cow::Owned(data) => data,
|
|
||||||
Cow::Borrowed(_) => unreachable!("must never happen"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let headers = pop_first_column(data);
|
|
||||||
let headers = headers
|
let headers = headers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|value| match value {
|
.map(|value| match value {
|
||||||
|
@ -727,23 +735,25 @@ fn transpose_table(layer: &mut RecordLayer<'_>) {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let data = _transpose_table(data, count_rows, count_columns - 1);
|
let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
|
||||||
|
|
||||||
layer.records = Cow::Owned(data);
|
layer.record_values = data;
|
||||||
layer.columns = Cow::Owned(headers);
|
layer.column_names = headers;
|
||||||
} else {
|
|
||||||
let mut data = _transpose_table(&layer.records, count_rows, count_columns);
|
|
||||||
|
|
||||||
for (column, column_name) in layer.columns.iter().enumerate() {
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
layer.was_transposed = !layer.was_transposed;
|
layer.was_transposed = !layer.was_transposed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,3 +780,9 @@ fn _transpose_table(
|
||||||
|
|
||||||
data
|
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,
|
text::Span,
|
||||||
widgets::{Block, Borders, Paragraph, StatefulWidget, Widget},
|
widgets::{Block, Borders, Paragraph, StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::cmp::{max, Ordering};
|
||||||
borrow::Cow,
|
|
||||||
cmp::{max, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableWidget<'a> {
|
pub struct TableWidget<'a> {
|
||||||
columns: Cow<'a, [String]>,
|
columns: &'a [String],
|
||||||
data: Cow<'a, [Vec<NuText>]>,
|
data: &'a [Vec<NuText>],
|
||||||
index_row: usize,
|
index_row: usize,
|
||||||
index_column: usize,
|
index_column: usize,
|
||||||
config: TableConfig,
|
config: TableConfig,
|
||||||
|
@ -39,8 +36,8 @@ pub enum Orientation {
|
||||||
impl<'a> TableWidget<'a> {
|
impl<'a> TableWidget<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
columns: impl Into<Cow<'a, [String]>>,
|
columns: &'a [String],
|
||||||
data: impl Into<Cow<'a, [Vec<NuText>]>>,
|
data: &'a [Vec<NuText>],
|
||||||
style_computer: &'a StyleComputer<'a>,
|
style_computer: &'a StyleComputer<'a>,
|
||||||
index_row: usize,
|
index_row: usize,
|
||||||
index_column: usize,
|
index_column: usize,
|
||||||
|
@ -48,8 +45,8 @@ impl<'a> TableWidget<'a> {
|
||||||
header_position: Orientation,
|
header_position: Orientation,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
columns: columns.into(),
|
columns,
|
||||||
data: data.into(),
|
data,
|
||||||
style_computer,
|
style_computer,
|
||||||
index_row,
|
index_row,
|
||||||
index_column,
|
index_column,
|
||||||
|
@ -197,22 +194,25 @@ impl<'a> TableWidget<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_head {
|
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 {
|
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();
|
||||||
|
|
||||||
let mut w = width;
|
let mut w = width;
|
||||||
w += render_space(buf, w, head_y, 1, padding_l);
|
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);
|
w += render_space(buf, w, head_y, 1, padding_r);
|
||||||
|
|
||||||
let x = w - padding_r - use_space;
|
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 head_rows = column.iter().map(|(t, s)| (t, *s));
|
||||||
|
|
||||||
width += render_space(buf, width, data_y, data_height, padding_l);
|
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, head_rows);
|
||||||
width += render_space(buf, width, data_y, data_height, padding_r);
|
width += render_space(buf, width, data_y, data_height, padding_r);
|
||||||
|
|
||||||
for (row, (text, _)) in column.iter().enumerate() {
|
for (row, (text, _)) in column.iter().enumerate() {
|
||||||
|
@ -305,10 +305,9 @@ impl<'a> TableWidget<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let columns = columns
|
let columns_iter = columns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| head_row_text(s, self.style_computer))
|
.map(|s| (s.clone(), head_style(s, self.style_computer)));
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if !show_index {
|
if !show_index {
|
||||||
let x = area.x + left_w;
|
let x = area.x + left_w;
|
||||||
|
@ -326,12 +325,12 @@ impl<'a> TableWidget<'a> {
|
||||||
let x = area.x + left_w;
|
let x = area.x + left_w;
|
||||||
left_w += render_space(buf, x, area.y, 1, padding_l);
|
left_w += render_space(buf, x, area.y, 1, padding_l);
|
||||||
let x = area.x + left_w;
|
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;
|
let x = area.x + left_w;
|
||||||
left_w += render_space(buf, x, area.y, 1, padding_r);
|
left_w += render_space(buf, x, area.y, 1, padding_r);
|
||||||
|
|
||||||
let layout_x = left_w - padding_r - columns_width as u16;
|
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
|
state
|
||||||
.layout
|
.layout
|
||||||
.push(text, layout_x, area.y + i as u16, columns_width as u16, 1);
|
.push(text, layout_x, area.y + i as u16, columns_width as u16, 1);
|
||||||
|
@ -377,10 +376,12 @@ impl<'a> TableWidget<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let head_rows = column.iter().map(|(t, s)| (t, *s));
|
||||||
|
|
||||||
let x = area.x + left_w;
|
let x = area.x + left_w;
|
||||||
left_w += render_space(buf, x, area.y, area.height, padding_l);
|
left_w += render_space(buf, x, area.y, area.height, padding_l);
|
||||||
let x = area.x + left_w;
|
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;
|
let x = area.x + left_w;
|
||||||
left_w += render_space(buf, x, area.y, area.height, padding_r);
|
left_w += render_space(buf, x, area.y, area.height, padding_r);
|
||||||
|
|
||||||
|
@ -619,14 +620,14 @@ fn repeat_vertical(
|
||||||
c: char,
|
c: char,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
) {
|
) {
|
||||||
let text = std::iter::repeat(c)
|
let text = String::from(c);
|
||||||
.take(width as usize)
|
|
||||||
.collect::<String>();
|
|
||||||
let style = text_style_to_tui_style(style);
|
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 {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,35 +677,28 @@ fn calculate_column_width(column: &[NuText]) -> usize {
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_column(
|
fn render_column<T, S>(
|
||||||
buf: &mut ratatui::buffer::Buffer,
|
buf: &mut ratatui::buffer::Buffer,
|
||||||
x: u16,
|
x: u16,
|
||||||
y: u16,
|
y: u16,
|
||||||
available_width: u16,
|
available_width: u16,
|
||||||
rows: &[NuText],
|
rows: impl Iterator<Item = (T, S)>,
|
||||||
) -> u16 {
|
) -> u16
|
||||||
for (row, (text, style)) in rows.iter().enumerate() {
|
where
|
||||||
let style = text_style_to_tui_style(*style);
|
T: AsRef<str>,
|
||||||
let text = strip_string(text);
|
S: Into<TextStyle>,
|
||||||
let span = Span::styled(text, style);
|
{
|
||||||
|
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);
|
buf.set_span(x, y + row as u16, &span, available_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
available_width
|
available_width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_string(text: &str) -> String {
|
fn head_style(head: &str, style_computer: &StyleComputer) -> TextStyle {
|
||||||
String::from_utf8(strip_ansi_escapes::strip(text))
|
let style =
|
||||||
.map_err(|_| ())
|
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown()));
|
||||||
.unwrap_or_else(|_| text.to_owned())
|
TextStyle::with_style(Alignment::Center, style)
|
||||||
}
|
|
||||||
|
|
||||||
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())),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,21 +16,21 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
pub struct TryView<'a> {
|
pub struct TryView {
|
||||||
input: Value,
|
input: Value,
|
||||||
command: String,
|
command: String,
|
||||||
reactive: bool,
|
immediate: bool,
|
||||||
table: Option<RecordView<'a>>,
|
table: Option<RecordView>,
|
||||||
view_mode: bool,
|
view_mode: bool,
|
||||||
border_color: Style,
|
border_color: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryView<'a> {
|
impl TryView {
|
||||||
pub fn new(input: Value) -> Self {
|
pub fn new(input: Value) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input,
|
input,
|
||||||
table: None,
|
table: None,
|
||||||
reactive: false,
|
immediate: false,
|
||||||
border_color: Style::default(),
|
border_color: Style::default(),
|
||||||
view_mode: false,
|
view_mode: false,
|
||||||
command: String::new(),
|
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) {
|
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||||
let border_color = self.border_color;
|
let border_color = self.border_color;
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ impl View for TryView<'_> {
|
||||||
if !self.command.is_empty() {
|
if !self.command.is_empty() {
|
||||||
self.command.pop();
|
self.command.pop();
|
||||||
|
|
||||||
if self.reactive {
|
if self.immediate {
|
||||||
match self.try_run(engine_state, stack) {
|
match self.try_run(engine_state, stack) {
|
||||||
Ok(_) => info.report = Some(Report::default()),
|
Ok(_) => info.report = Some(Report::default()),
|
||||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||||
|
@ -191,7 +191,7 @@ impl View for TryView<'_> {
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
self.command.push(*c);
|
self.command.push(*c);
|
||||||
|
|
||||||
if self.reactive {
|
if self.immediate {
|
||||||
match self.try_run(engine_state, stack) {
|
match self.try_run(engine_state, stack) {
|
||||||
Ok(_) => info.report = Some(Report::default()),
|
Ok(_) => info.report = Some(Report::default()),
|
||||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||||
|
@ -235,7 +235,7 @@ impl View for TryView<'_> {
|
||||||
|
|
||||||
fn setup(&mut self, config: ViewConfig<'_>) {
|
fn setup(&mut self, config: ViewConfig<'_>) {
|
||||||
self.border_color = nu_style_to_tui(config.explore_config.table.separator_style);
|
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![]);
|
let mut r = RecordView::new(vec![], vec![]);
|
||||||
r.setup(config);
|
r.setup(config);
|
||||||
|
@ -253,7 +253,7 @@ fn run_command(
|
||||||
input: &Value,
|
input: &Value,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<RecordView<'static>> {
|
) -> Result<RecordView> {
|
||||||
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
|
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
|
||||||
|
|
||||||
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));
|
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));
|
||||||
|
|
|
@ -6,18 +6,36 @@ pub fn home_dir() -> Option<PathBuf> {
|
||||||
dirs_next::home_dir()
|
dirs_next::home_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the data directory for the current platform or XDG_DATA_HOME if specified.
|
||||||
|
pub fn data_dir() -> Option<PathBuf> {
|
||||||
|
match std::env::var("XDG_DATA_HOME").map(PathBuf::from) {
|
||||||
|
Ok(xdg_data) if xdg_data.is_absolute() => Some(canonicalize(&xdg_data).unwrap_or(xdg_data)),
|
||||||
|
_ => get_canonicalized_path(dirs_next::data_dir()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the cache directory for the current platform or XDG_CACHE_HOME if specified.
|
||||||
|
pub fn cache_dir() -> Option<PathBuf> {
|
||||||
|
match std::env::var("XDG_CACHE_HOME").map(PathBuf::from) {
|
||||||
|
Ok(xdg_cache) if xdg_cache.is_absolute() => {
|
||||||
|
Some(canonicalize(&xdg_cache).unwrap_or(xdg_cache))
|
||||||
|
}
|
||||||
|
_ => get_canonicalized_path(dirs_next::cache_dir()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the config directory for the current platform or XDG_CONFIG_HOME if specified.
|
||||||
pub fn config_dir() -> Option<PathBuf> {
|
pub fn config_dir() -> Option<PathBuf> {
|
||||||
match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) {
|
match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) {
|
||||||
Ok(xdg_config) if xdg_config.is_absolute() => {
|
Ok(xdg_config) if xdg_config.is_absolute() => {
|
||||||
Some(canonicalize(&xdg_config).unwrap_or(xdg_config))
|
Some(canonicalize(&xdg_config).unwrap_or(xdg_config))
|
||||||
}
|
}
|
||||||
_ => config_dir_old(),
|
_ => get_canonicalized_path(dirs_next::config_dir()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the old default config directory. Outside of Linux, this will ignore `XDG_CONFIG_HOME`
|
pub fn get_canonicalized_path(path: Option<PathBuf>) -> Option<PathBuf> {
|
||||||
pub fn config_dir_old() -> Option<PathBuf> {
|
let path = path?;
|
||||||
let path = dirs_next::config_dir()?;
|
|
||||||
Some(canonicalize(&path).unwrap_or(path))
|
Some(canonicalize(&path).unwrap_or(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,6 @@ mod trailing_slash;
|
||||||
|
|
||||||
pub use components::components;
|
pub use components::components;
|
||||||
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
|
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
|
||||||
pub use helpers::{config_dir, config_dir_old, home_dir};
|
pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir};
|
||||||
pub use tilde::expand_tilde;
|
pub use tilde::expand_tilde;
|
||||||
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};
|
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};
|
||||||
|
|
|
@ -16,11 +16,13 @@ bench = false
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||||
|
nu-derive-value = { path = "../nu-derive-value", version = "0.94.3" }
|
||||||
|
|
||||||
brotli = { workspace = true, optional = true }
|
brotli = { workspace = true, optional = true }
|
||||||
byte-unit = { version = "5.1", features = [ "serde" ] }
|
byte-unit = { version = "5.1", features = [ "serde" ] }
|
||||||
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||||
chrono-humanize = { workspace = true }
|
chrono-humanize = { workspace = true }
|
||||||
|
convert_case = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
lru = { workspace = true }
|
lru = { workspace = true }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Argument, Block, Expr, ExternalArgument, ImportPattern, MatchPattern, RecordItem},
|
ast::{Argument, Block, Expr, ExternalArgument, ImportPattern, MatchPattern, RecordItem},
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::StateWorkingSet,
|
||||||
BlockId, DeclId, Signature, Span, SpanId, Type, VarId, IN_VARIABLE_ID,
|
BlockId, DeclId, GetSpan, Signature, Span, SpanId, Type, VarId, IN_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -516,7 +516,7 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(&self, engine_state: &EngineState) -> Span {
|
pub fn span(&self, state: &impl GetSpan) -> Span {
|
||||||
engine_state.get_span(self.span_id)
|
state.get_span(self.span_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
Variable, Visibility, DEFAULT_OVERLAY_NAME,
|
Variable, Visibility, DEFAULT_OVERLAY_NAME,
|
||||||
},
|
},
|
||||||
eval_const::create_nu_constant,
|
eval_const::create_nu_constant,
|
||||||
BlockId, Category, Config, DeclId, FileId, HistoryConfig, Module, ModuleId, OverlayId,
|
BlockId, Category, Config, DeclId, FileId, GetSpan, HistoryConfig, Module, ModuleId, OverlayId,
|
||||||
ShellError, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId,
|
ShellError, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId,
|
||||||
};
|
};
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
|
@ -1035,18 +1035,20 @@ impl EngineState {
|
||||||
SpanId(self.num_spans() - 1)
|
SpanId(self.num_spans() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find ID of a span (should be avoided if possible)
|
||||||
|
pub fn find_span_id(&self, span: Span) -> Option<SpanId> {
|
||||||
|
self.spans.iter().position(|sp| sp == &span).map(SpanId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GetSpan for &'a EngineState {
|
||||||
/// Get existing span
|
/// Get existing span
|
||||||
pub fn get_span(&self, span_id: SpanId) -> Span {
|
fn get_span(&self, span_id: SpanId) -> Span {
|
||||||
*self
|
*self
|
||||||
.spans
|
.spans
|
||||||
.get(span_id.0)
|
.get(span_id.0)
|
||||||
.expect("internal error: missing span")
|
.expect("internal error: missing span")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find ID of a span (should be avoided if possible)
|
|
||||||
pub fn find_span_id(&self, span: Span) -> Option<SpanId> {
|
|
||||||
self.spans.iter().position(|sp| sp == &span).map(SpanId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EngineState {
|
impl Default for EngineState {
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
||||||
usage::build_usage, CachedFile, Command, CommandType, EngineState, OverlayFrame,
|
usage::build_usage, CachedFile, Command, CommandType, EngineState, OverlayFrame,
|
||||||
StateDelta, Variable, VirtualPath, Visibility,
|
StateDelta, Variable, VirtualPath, Visibility,
|
||||||
},
|
},
|
||||||
BlockId, Category, Config, DeclId, FileId, Module, ModuleId, ParseError, ParseWarning, Span,
|
BlockId, Category, Config, DeclId, FileId, GetSpan, Module, ModuleId, ParseError, ParseWarning,
|
||||||
SpanId, Type, Value, VarId, VirtualPathId,
|
Span, SpanId, Type, Value, VarId, VirtualPathId,
|
||||||
};
|
};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -1019,8 +1019,10 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
self.delta.spans.push(span);
|
self.delta.spans.push(span);
|
||||||
SpanId(num_permanent_spans + self.delta.spans.len() - 1)
|
SpanId(num_permanent_spans + self.delta.spans.len() - 1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_span(&self, span_id: SpanId) -> Span {
|
impl<'a> GetSpan for &'a StateWorkingSet<'a> {
|
||||||
|
fn get_span(&self, span_id: SpanId) -> Span {
|
||||||
let num_permanent_spans = self.permanent_state.num_spans();
|
let num_permanent_spans = self.permanent_state.num_spans();
|
||||||
if span_id.0 < num_permanent_spans {
|
if span_id.0 < num_permanent_spans {
|
||||||
self.permanent_state.get_span(span_id)
|
self.permanent_state.get_span(span_id)
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub enum ParseWarning {
|
||||||
DeprecatedWarning {
|
DeprecatedWarning {
|
||||||
old_command: String,
|
old_command: String,
|
||||||
new_suggestion: String,
|
new_suggestion: String,
|
||||||
#[label("`{old_command}` is deprecated and will be removed in 0.94. Please {new_suggestion} instead")]
|
#[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead")]
|
||||||
span: Span,
|
span: Span,
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
|
|
|
@ -557,12 +557,12 @@ pub enum ShellError {
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
///
|
///
|
||||||
/// Check the spelling of your column name. Did you forget to rename a column somewhere?
|
/// Check the spelling of your column name. Did you forget to rename a column somewhere?
|
||||||
#[error("Cannot find column")]
|
#[error("Cannot find column '{col_name}'")]
|
||||||
#[diagnostic(code(nu::shell::column_not_found))]
|
#[diagnostic(code(nu::shell::column_not_found))]
|
||||||
CantFindColumn {
|
CantFindColumn {
|
||||||
col_name: String,
|
col_name: String,
|
||||||
#[label = "cannot find column '{col_name}'"]
|
#[label = "cannot find column '{col_name}'"]
|
||||||
span: Span,
|
span: Option<Span>,
|
||||||
#[label = "value originates here"]
|
#[label = "value originates here"]
|
||||||
src_span: Span,
|
src_span: Span,
|
||||||
},
|
},
|
||||||
|
@ -876,6 +876,21 @@ pub enum ShellError {
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
/// An I/O operation failed.
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// This is a generic error. Refer to the specific error message for further details.
|
||||||
|
#[error("program coredump error")]
|
||||||
|
#[diagnostic(code(nu::shell::coredump_error))]
|
||||||
|
CoredumpErrorSpanned {
|
||||||
|
msg: String,
|
||||||
|
signal: i32,
|
||||||
|
#[label("{msg}")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// Tried to `cd` to a path that isn't a directory.
|
/// Tried to `cd` to a path that isn't a directory.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
ExternalArgument, ListItem, Math, Operator, RecordItem,
|
ExternalArgument, ListItem, Math, Operator, RecordItem,
|
||||||
},
|
},
|
||||||
debugger::DebugContext,
|
debugger::DebugContext,
|
||||||
Config, Range, Record, ShellError, Span, Type, Value, VarId, ENV_VARIABLE_ID,
|
Config, GetSpan, Range, Record, ShellError, Span, Type, Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use std::{borrow::Cow, collections::HashMap};
|
||||||
pub trait Eval {
|
pub trait Eval {
|
||||||
/// State that doesn't need to be mutated.
|
/// State that doesn't need to be mutated.
|
||||||
/// EngineState for regular eval and StateWorkingSet for const eval
|
/// EngineState for regular eval and StateWorkingSet for const eval
|
||||||
type State<'a>: Copy;
|
type State<'a>: Copy + GetSpan;
|
||||||
|
|
||||||
/// State that needs to be mutated.
|
/// State that needs to be mutated.
|
||||||
/// This is the stack for regular eval, and unused by const eval
|
/// This is the stack for regular eval, and unused by const eval
|
||||||
|
@ -23,17 +23,19 @@ pub trait Eval {
|
||||||
mut_state: &mut Self::MutState,
|
mut_state: &mut Self::MutState,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
|
let expr_span = expr.span(&state);
|
||||||
|
|
||||||
match &expr.expr {
|
match &expr.expr {
|
||||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
Expr::Bool(b) => Ok(Value::bool(*b, expr_span)),
|
||||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
Expr::Int(i) => Ok(Value::int(*i, expr_span)),
|
||||||
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
Expr::Float(f) => Ok(Value::float(*f, expr_span)),
|
||||||
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr_span)),
|
||||||
Expr::Filepath(path, quoted) => Self::eval_filepath(state, mut_state, path.clone(), *quoted, expr.span),
|
Expr::Filepath(path, quoted) => Self::eval_filepath(state, mut_state, path.clone(), *quoted, expr_span),
|
||||||
Expr::Directory(path, quoted) => {
|
Expr::Directory(path, quoted) => {
|
||||||
Self::eval_directory(state, mut_state, path.clone(), *quoted, expr.span)
|
Self::eval_directory(state, mut_state, path.clone(), *quoted, expr_span)
|
||||||
}
|
}
|
||||||
Expr::Var(var_id) => Self::eval_var(state, mut_state, *var_id, expr.span),
|
Expr::Var(var_id) => Self::eval_var(state, mut_state, *var_id, expr_span),
|
||||||
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
|
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr_span)),
|
||||||
Expr::FullCellPath(cell_path) => {
|
Expr::FullCellPath(cell_path) => {
|
||||||
let value = Self::eval::<D>(state, mut_state, &cell_path.head)?;
|
let value = Self::eval::<D>(state, mut_state, &cell_path.head)?;
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ pub trait Eval {
|
||||||
value.follow_cell_path(&cell_path.tail, false)
|
value.follow_cell_path(&cell_path.tail, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
|
Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)),
|
||||||
Expr::List(list) => {
|
Expr::List(list) => {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for item in list {
|
for item in list {
|
||||||
|
@ -53,11 +55,11 @@ pub trait Eval {
|
||||||
ListItem::Item(expr) => output.push(Self::eval::<D>(state, mut_state, expr)?),
|
ListItem::Item(expr) => output.push(Self::eval::<D>(state, mut_state, expr)?),
|
||||||
ListItem::Spread(_, expr) => match Self::eval::<D>(state, mut_state, expr)? {
|
ListItem::Spread(_, expr) => match Self::eval::<D>(state, mut_state, expr)? {
|
||||||
Value::List { vals, .. } => output.extend(vals),
|
Value::List { vals, .. } => output.extend(vals),
|
||||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
_ => return Err(ShellError::CannotSpreadAsList { span: expr_span }),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Value::list(output, expr.span))
|
Ok(Value::list(output, expr_span))
|
||||||
}
|
}
|
||||||
Expr::Record(items) => {
|
Expr::Record(items) => {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
|
@ -67,36 +69,38 @@ pub trait Eval {
|
||||||
RecordItem::Pair(col, val) => {
|
RecordItem::Pair(col, val) => {
|
||||||
// avoid duplicate cols
|
// avoid duplicate cols
|
||||||
let col_name = Self::eval::<D>(state, mut_state, col)?.coerce_into_string()?;
|
let col_name = Self::eval::<D>(state, mut_state, col)?.coerce_into_string()?;
|
||||||
|
let col_span = col.span(&state);
|
||||||
if let Some(orig_span) = col_names.get(&col_name) {
|
if let Some(orig_span) = col_names.get(&col_name) {
|
||||||
return Err(ShellError::ColumnDefinedTwice {
|
return Err(ShellError::ColumnDefinedTwice {
|
||||||
col_name,
|
col_name,
|
||||||
second_use: col.span,
|
second_use: col_span,
|
||||||
first_use: *orig_span,
|
first_use: *orig_span,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
col_names.insert(col_name.clone(), col.span);
|
col_names.insert(col_name.clone(), col_span);
|
||||||
record.push(col_name, Self::eval::<D>(state, mut_state, val)?);
|
record.push(col_name, Self::eval::<D>(state, mut_state, val)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecordItem::Spread(_, inner) => {
|
RecordItem::Spread(_, inner) => {
|
||||||
|
let inner_span = inner.span(&state);
|
||||||
match Self::eval::<D>(state, mut_state, inner)? {
|
match Self::eval::<D>(state, mut_state, inner)? {
|
||||||
Value::Record { val: inner_val, .. } => {
|
Value::Record { val: inner_val, .. } => {
|
||||||
for (col_name, val) in inner_val.into_owned() {
|
for (col_name, val) in inner_val.into_owned() {
|
||||||
if let Some(orig_span) = col_names.get(&col_name) {
|
if let Some(orig_span) = col_names.get(&col_name) {
|
||||||
return Err(ShellError::ColumnDefinedTwice {
|
return Err(ShellError::ColumnDefinedTwice {
|
||||||
col_name,
|
col_name,
|
||||||
second_use: inner.span,
|
second_use: inner_span,
|
||||||
first_use: *orig_span,
|
first_use: *orig_span,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
col_names.insert(col_name.clone(), inner.span);
|
col_names.insert(col_name.clone(), inner_span);
|
||||||
record.push(col_name, val);
|
record.push(col_name, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::CannotSpreadAsRecord {
|
return Err(ShellError::CannotSpreadAsRecord {
|
||||||
span: inner.span,
|
span: inner_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +108,7 @@ pub trait Eval {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::record(record, expr.span))
|
Ok(Value::record(record, expr_span))
|
||||||
}
|
}
|
||||||
Expr::Table(table) => {
|
Expr::Table(table) => {
|
||||||
let mut output_headers = vec![];
|
let mut output_headers = vec![];
|
||||||
|
@ -114,10 +118,11 @@ pub trait Eval {
|
||||||
.iter()
|
.iter()
|
||||||
.position(|existing| existing == &header)
|
.position(|existing| existing == &header)
|
||||||
{
|
{
|
||||||
|
let first_use = table.columns[idx].span(&state);
|
||||||
return Err(ShellError::ColumnDefinedTwice {
|
return Err(ShellError::ColumnDefinedTwice {
|
||||||
col_name: header,
|
col_name: header,
|
||||||
second_use: expr.span,
|
second_use: expr_span,
|
||||||
first_use: table.columns[idx].span,
|
first_use,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
output_headers.push(header);
|
output_headers.push(header);
|
||||||
|
@ -132,66 +137,66 @@ pub trait Eval {
|
||||||
|
|
||||||
output_rows.push(Value::record(
|
output_rows.push(Value::record(
|
||||||
record,
|
record,
|
||||||
expr.span,
|
expr_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(Value::list(output_rows, expr.span))
|
Ok(Value::list(output_rows, expr_span))
|
||||||
}
|
}
|
||||||
Expr::Keyword(kw) => Self::eval::<D>(state, mut_state, &kw.expr),
|
Expr::Keyword(kw) => Self::eval::<D>(state, mut_state, &kw.expr),
|
||||||
Expr::String(s) | Expr::RawString(s) => Ok(Value::string(s.clone(), expr.span)),
|
Expr::String(s) | Expr::RawString(s) => Ok(Value::string(s.clone(), expr_span)),
|
||||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
Expr::Nothing => Ok(Value::nothing(expr_span)),
|
||||||
Expr::ValueWithUnit(value) => match Self::eval::<D>(state, mut_state, &value.expr)? {
|
Expr::ValueWithUnit(value) => match Self::eval::<D>(state, mut_state, &value.expr)? {
|
||||||
Value::Int { val, .. } => value.unit.item.build_value(val, value.unit.span),
|
Value::Int { val, .. } => value.unit.item.build_value(val, value.unit.span),
|
||||||
x => Err(ShellError::CantConvert {
|
x => Err(ShellError::CantConvert {
|
||||||
to_type: "unit value".into(),
|
to_type: "unit value".into(),
|
||||||
from_type: x.get_type().to_string(),
|
from_type: x.get_type().to_string(),
|
||||||
span: value.expr.span,
|
span: value.expr.span(&state),
|
||||||
help: None,
|
help: None,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Expr::Call(call) => Self::eval_call::<D>(state, mut_state, call, expr.span),
|
Expr::Call(call) => Self::eval_call::<D>(state, mut_state, call, expr_span),
|
||||||
Expr::ExternalCall(head, args) => {
|
Expr::ExternalCall(head, args) => {
|
||||||
Self::eval_external_call(state, mut_state, head, args, expr.span)
|
Self::eval_external_call(state, mut_state, head, args, expr_span)
|
||||||
}
|
}
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr.span)
|
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr_span)
|
||||||
}
|
}
|
||||||
Expr::Range(range) => {
|
Expr::Range(range) => {
|
||||||
let from = if let Some(f) = &range.from {
|
let from = if let Some(f) = &range.from {
|
||||||
Self::eval::<D>(state, mut_state, f)?
|
Self::eval::<D>(state, mut_state, f)?
|
||||||
} else {
|
} else {
|
||||||
Value::nothing(expr.span)
|
Value::nothing(expr_span)
|
||||||
};
|
};
|
||||||
|
|
||||||
let next = if let Some(s) = &range.next {
|
let next = if let Some(s) = &range.next {
|
||||||
Self::eval::<D>(state, mut_state, s)?
|
Self::eval::<D>(state, mut_state, s)?
|
||||||
} else {
|
} else {
|
||||||
Value::nothing(expr.span)
|
Value::nothing(expr_span)
|
||||||
};
|
};
|
||||||
|
|
||||||
let to = if let Some(t) = &range.to {
|
let to = if let Some(t) = &range.to {
|
||||||
Self::eval::<D>(state, mut_state, t)?
|
Self::eval::<D>(state, mut_state, t)?
|
||||||
} else {
|
} else {
|
||||||
Value::nothing(expr.span)
|
Value::nothing(expr_span)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Value::range(
|
Ok(Value::range(
|
||||||
Range::new(from, next, to, range.operator.inclusion, expr.span)?,
|
Range::new(from, next, to, range.operator.inclusion, expr_span)?,
|
||||||
expr.span,
|
expr_span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Expr::UnaryNot(expr) => {
|
Expr::UnaryNot(expr) => {
|
||||||
let lhs = Self::eval::<D>(state, mut_state, expr)?;
|
let lhs = Self::eval::<D>(state, mut_state, expr)?;
|
||||||
match lhs {
|
match lhs {
|
||||||
Value::Bool { val, .. } => Ok(Value::bool(!val, expr.span)),
|
Value::Bool { val, .. } => Ok(Value::bool(!val, expr_span)),
|
||||||
other => Err(ShellError::TypeMismatch {
|
other => Err(ShellError::TypeMismatch {
|
||||||
err_message: format!("expected bool, found {}", other.get_type()),
|
err_message: format!("expected bool, found {}", other.get_type()),
|
||||||
span: expr.span,
|
span: expr_span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::BinaryOp(lhs, op, rhs) => {
|
Expr::BinaryOp(lhs, op, rhs) => {
|
||||||
let op_span = op.span;
|
let op_span = op.span(&state);
|
||||||
let op = eval_operator(op)?;
|
let op = eval_operator(op)?;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
|
@ -200,23 +205,23 @@ pub trait Eval {
|
||||||
match boolean {
|
match boolean {
|
||||||
Boolean::And => {
|
Boolean::And => {
|
||||||
if lhs.is_false() {
|
if lhs.is_false() {
|
||||||
Ok(Value::bool(false, expr.span))
|
Ok(Value::bool(false, expr_span))
|
||||||
} else {
|
} else {
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
lhs.and(op_span, &rhs, expr.span)
|
lhs.and(op_span, &rhs, expr_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Boolean::Or => {
|
Boolean::Or => {
|
||||||
if lhs.is_true() {
|
if lhs.is_true() {
|
||||||
Ok(Value::bool(true, expr.span))
|
Ok(Value::bool(true, expr_span))
|
||||||
} else {
|
} else {
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
lhs.or(op_span, &rhs, expr.span)
|
lhs.or(op_span, &rhs, expr_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Boolean::Xor => {
|
Boolean::Xor => {
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
lhs.xor(op_span, &rhs, expr.span)
|
lhs.xor(op_span, &rhs, expr_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,35 +230,35 @@ pub trait Eval {
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
|
|
||||||
match math {
|
match math {
|
||||||
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
Math::Plus => lhs.add(op_span, &rhs, expr_span),
|
||||||
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
Math::Minus => lhs.sub(op_span, &rhs, expr_span),
|
||||||
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
Math::Multiply => lhs.mul(op_span, &rhs, expr_span),
|
||||||
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
Math::Divide => lhs.div(op_span, &rhs, expr_span),
|
||||||
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
Math::Append => lhs.append(op_span, &rhs, expr_span),
|
||||||
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
Math::Modulo => lhs.modulo(op_span, &rhs, expr_span),
|
||||||
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr_span),
|
||||||
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
Math::Pow => lhs.pow(op_span, &rhs, expr_span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operator::Comparison(comparison) => {
|
Operator::Comparison(comparison) => {
|
||||||
let lhs = Self::eval::<D>(state, mut_state, lhs)?;
|
let lhs = Self::eval::<D>(state, mut_state, lhs)?;
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
match comparison {
|
match comparison {
|
||||||
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
Comparison::LessThan => lhs.lt(op_span, &rhs, expr_span),
|
||||||
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr_span),
|
||||||
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr_span),
|
||||||
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr_span),
|
||||||
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
Comparison::Equal => lhs.eq(op_span, &rhs, expr_span),
|
||||||
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr_span),
|
||||||
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
Comparison::In => lhs.r#in(op_span, &rhs, expr_span),
|
||||||
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr_span),
|
||||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr_span),
|
||||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr_span),
|
||||||
Comparison::RegexMatch => {
|
Comparison::RegexMatch => {
|
||||||
Self::regex_match(state, op_span, &lhs, &rhs, false, expr.span)
|
Self::regex_match(state, op_span, &lhs, &rhs, false, expr_span)
|
||||||
}
|
}
|
||||||
Comparison::NotRegexMatch => {
|
Comparison::NotRegexMatch => {
|
||||||
Self::regex_match(state, op_span, &lhs, &rhs, true, expr.span)
|
Self::regex_match(state, op_span, &lhs, &rhs, true, expr_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,20 +266,20 @@ pub trait Eval {
|
||||||
let lhs = Self::eval::<D>(state, mut_state, lhs)?;
|
let lhs = Self::eval::<D>(state, mut_state, lhs)?;
|
||||||
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
|
||||||
match bits {
|
match bits {
|
||||||
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr_span),
|
||||||
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr_span),
|
||||||
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr_span),
|
||||||
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr_span),
|
||||||
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr_span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operator::Assignment(assignment) => Self::eval_assignment::<D>(
|
Operator::Assignment(assignment) => Self::eval_assignment::<D>(
|
||||||
state, mut_state, lhs, rhs, assignment, op_span, expr.span
|
state, mut_state, lhs, rhs, assignment, op_span, expr_span
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
|
||||||
Self::eval_row_condition_or_closure(state, mut_state, *block_id, expr.span)
|
Self::eval_row_condition_or_closure(state, mut_state, *block_id, expr_span)
|
||||||
}
|
}
|
||||||
Expr::StringInterpolation(exprs, quoted) => {
|
Expr::StringInterpolation(exprs, quoted) => {
|
||||||
let config = Self::get_config(state, mut_state);
|
let config = Self::get_config(state, mut_state);
|
||||||
|
@ -286,16 +291,16 @@ pub trait Eval {
|
||||||
// For supporting bare string interpolation to create globs, currently only
|
// For supporting bare string interpolation to create globs, currently only
|
||||||
// implemented for external calls
|
// implemented for external calls
|
||||||
if expr.ty == Type::Glob {
|
if expr.ty == Type::Glob {
|
||||||
Ok(Value::glob(str, *quoted, expr.span))
|
Ok(Value::glob(str, *quoted, expr_span))
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::string(str, expr.span))
|
Ok(Value::string(str, expr_span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Overlay(_) => Self::eval_overlay(state, expr.span),
|
Expr::Overlay(_) => Self::eval_overlay(state, expr_span),
|
||||||
Expr::GlobPattern(pattern, quoted) => {
|
Expr::GlobPattern(pattern, quoted) => {
|
||||||
// GlobPattern is similar to Filepath
|
// GlobPattern is similar to Filepath
|
||||||
// But we don't want to expand path during eval time, it's required for `nu_engine::glob_from` to run correctly
|
// But we don't want to expand path during eval time, it's required for `nu_engine::glob_from` to run correctly
|
||||||
Ok(Value::glob(pattern, *quoted, expr.span))
|
Ok(Value::glob(pattern, *quoted, expr_span))
|
||||||
}
|
}
|
||||||
Expr::MatchBlock(_) // match blocks are handled by `match`
|
Expr::MatchBlock(_) // match blocks are handled by `match`
|
||||||
| Expr::Block(_) // blocks are handled directly by core commands
|
| Expr::Block(_) // blocks are handled directly by core commands
|
||||||
|
@ -303,7 +308,7 @@ pub trait Eval {
|
||||||
| Expr::ImportPattern(_)
|
| Expr::ImportPattern(_)
|
||||||
| Expr::Signature(_)
|
| Expr::Signature(_)
|
||||||
| Expr::Operator(_)
|
| Expr::Operator(_)
|
||||||
| Expr::Garbage => Self::unreachable(expr),
|
| Expr::Garbage => Self::unreachable(state, expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,5 +389,5 @@ pub trait Eval {
|
||||||
fn eval_overlay(state: Self::State<'_>, span: Span) -> Result<Value, ShellError>;
|
fn eval_overlay(state: Self::State<'_>, span: Span) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
/// For expressions that should never actually be evaluated
|
/// For expressions that should never actually be evaluated
|
||||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError>;
|
fn unreachable(state: Self::State<'_>, expr: &Expression) -> Result<Value, ShellError>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,38 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
record.push(
|
||||||
|
"data-dir",
|
||||||
|
if let Some(path) = nu_path::data_dir() {
|
||||||
|
let mut canon_data_path = canonicalize_path(engine_state, &path);
|
||||||
|
canon_data_path.push("nushell");
|
||||||
|
Value::string(canon_data_path.to_string_lossy(), span)
|
||||||
|
} else {
|
||||||
|
Value::error(
|
||||||
|
ShellError::IOError {
|
||||||
|
msg: "Could not get data path".into(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
record.push(
|
||||||
|
"cache-dir",
|
||||||
|
if let Some(path) = nu_path::cache_dir() {
|
||||||
|
let mut canon_cache_path = canonicalize_path(engine_state, &path);
|
||||||
|
canon_cache_path.push("nushell");
|
||||||
|
Value::string(canon_cache_path.to_string_lossy(), span)
|
||||||
|
} else {
|
||||||
|
Value::error(
|
||||||
|
ShellError::IOError {
|
||||||
|
msg: "Could not get cache path".into(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
record.push("temp-path", {
|
record.push("temp-path", {
|
||||||
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
|
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
|
||||||
Value::string(canon_temp_path.to_string_lossy(), span)
|
Value::string(canon_temp_path.to_string_lossy(), span)
|
||||||
|
@ -251,7 +283,7 @@ pub fn eval_constant_with_input(
|
||||||
Expr::Call(call) => eval_const_call(working_set, call, input),
|
Expr::Call(call) => eval_const_call(working_set, call, input),
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
eval_const_subexpression(working_set, block, input, expr.span)
|
eval_const_subexpression(working_set, block, input, expr.span(&working_set))
|
||||||
}
|
}
|
||||||
_ => eval_constant(working_set, expr).map(|v| PipelineData::Value(v, None)),
|
_ => eval_constant(working_set, expr).map(|v| PipelineData::Value(v, None)),
|
||||||
}
|
}
|
||||||
|
@ -379,7 +411,9 @@ impl Eval for EvalConst {
|
||||||
Err(ShellError::NotAConstant { span })
|
Err(ShellError::NotAConstant { span })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
|
||||||
Err(ShellError::NotAConstant { span: expr.span })
|
Err(ShellError::NotAConstant {
|
||||||
|
span: expr.span(&working_set),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,3 +39,5 @@ pub use span::*;
|
||||||
pub use syntax_shape::*;
|
pub use syntax_shape::*;
|
||||||
pub use ty::*;
|
pub use ty::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|
||||||
|
pub use nu_derive_value::*;
|
||||||
|
|
|
@ -23,7 +23,21 @@ impl ExitStatusFuture {
|
||||||
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
||||||
ExitStatusFuture::Running(receiver) => {
|
ExitStatusFuture::Running(receiver) => {
|
||||||
let code = match receiver.recv() {
|
let code = match receiver.recv() {
|
||||||
Ok(Ok(status)) => Ok(status),
|
Ok(Ok(status)) => {
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let ExitStatus::Signaled {
|
||||||
|
signal,
|
||||||
|
core_dumped: true,
|
||||||
|
} = status
|
||||||
|
{
|
||||||
|
return Err(ShellError::CoredumpErrorSpanned {
|
||||||
|
msg: format!("coredump detected. received signal: {signal}"),
|
||||||
|
signal,
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
||||||
msg: format!("failed to get exit code: {err:?}"),
|
msg: format!("failed to get exit code: {err:?}"),
|
||||||
span,
|
span,
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
use crate::SpanId;
|
||||||
use miette::SourceSpan;
|
use miette::SourceSpan;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
pub trait GetSpan {
|
||||||
|
fn get_span(&self, span_id: SpanId) -> Span;
|
||||||
|
}
|
||||||
|
|
||||||
/// A spanned area of interest, generic over what kind of thing is of interest
|
/// A spanned area of interest, generic over what kind of thing is of interest
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct Spanned<T> {
|
pub struct Spanned<T> {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
196
crates/nu-protocol/src/value/into_value.rs
Normal file
196
crates/nu-protocol/src/value/into_value.rs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{Record, ShellError, Span, Value};
|
||||||
|
|
||||||
|
/// A trait for converting a value into a [`Value`].
|
||||||
|
///
|
||||||
|
/// This conversion is infallible, for fallible conversions use [`TryIntoValue`].
|
||||||
|
///
|
||||||
|
/// # Derivable
|
||||||
|
/// This trait can be used with `#[derive]`.
|
||||||
|
/// When derived on structs with named fields, the resulting value representation will use
|
||||||
|
/// [`Value::Record`], where each field of the record corresponds to a field of the struct.
|
||||||
|
/// For structs with unnamed fields, the value representation will be [`Value::List`], with all
|
||||||
|
/// fields inserted into a list.
|
||||||
|
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
||||||
|
///
|
||||||
|
/// Only enums with no fields may derive this trait.
|
||||||
|
/// The resulting value representation will be the name of the variant as a [`Value::String`].
|
||||||
|
/// By default, variant names will be converted to ["snake_case"](convert_case::Case::Snake).
|
||||||
|
/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum.
|
||||||
|
/// All deterministic and useful case conversions provided by [`convert_case::Case`] are supported
|
||||||
|
/// by specifying the case name followed by "case".
|
||||||
|
/// Also all values for
|
||||||
|
/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid
|
||||||
|
/// here.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{IntoValue, Value, Span};
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// #[nu_value(rename_all = "COBOL-CASE")]
|
||||||
|
/// enum Bird {
|
||||||
|
/// MountainEagle,
|
||||||
|
/// ForestOwl,
|
||||||
|
/// RiverDuck,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// Bird::RiverDuck.into_value(Span::unknown()),
|
||||||
|
/// Value::test_string("RIVER-DUCK")
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub trait IntoValue: Sized {
|
||||||
|
/// Converts the given value to a [`Value`].
|
||||||
|
fn into_value(self, span: Span) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitive Types
|
||||||
|
|
||||||
|
impl<T, const N: usize> IntoValue for [T; N]
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Vec::from(self).into_value(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitive_into_value {
|
||||||
|
($type:ty, $method:ident) => {
|
||||||
|
primitive_into_value!($type => $type, $method);
|
||||||
|
};
|
||||||
|
|
||||||
|
($type:ty => $as_type:ty, $method:ident) => {
|
||||||
|
impl IntoValue for $type {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::$method(<$as_type>::from(self), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive_into_value!(bool, bool);
|
||||||
|
primitive_into_value!(char, string);
|
||||||
|
primitive_into_value!(f32 => f64, float);
|
||||||
|
primitive_into_value!(f64, float);
|
||||||
|
primitive_into_value!(i8 => i64, int);
|
||||||
|
primitive_into_value!(i16 => i64, int);
|
||||||
|
primitive_into_value!(i32 => i64, int);
|
||||||
|
primitive_into_value!(i64, int);
|
||||||
|
primitive_into_value!(u8 => i64, int);
|
||||||
|
primitive_into_value!(u16 => i64, int);
|
||||||
|
primitive_into_value!(u32 => i64, int);
|
||||||
|
// u64 and usize may be truncated as Value only supports i64.
|
||||||
|
|
||||||
|
impl IntoValue for isize {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::int(self as i64, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue for () {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::nothing(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tuple_into_value {
|
||||||
|
($($t:ident:$n:tt),+) => {
|
||||||
|
impl<$($t),+> IntoValue for ($($t,)+) where $($t: IntoValue,)+ {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
let vals = vec![$(self.$n.into_value(span)),+];
|
||||||
|
Value::list(vals, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuples in std are implemented for up to 12 elements, so we do it here too.
|
||||||
|
tuple_into_value!(T0:0);
|
||||||
|
tuple_into_value!(T0:0, T1:1);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11);
|
||||||
|
|
||||||
|
// Other std Types
|
||||||
|
|
||||||
|
impl IntoValue for String {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::string(self, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoValue for Vec<T>
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::list(self.into_iter().map(|v| v.into_value(span)).collect(), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoValue for Option<T>
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
match self {
|
||||||
|
Some(v) => v.into_value(span),
|
||||||
|
None => Value::nothing(span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoValue for HashMap<String, V>
|
||||||
|
where
|
||||||
|
V: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
let mut record = Record::new();
|
||||||
|
for (k, v) in self.into_iter() {
|
||||||
|
// Using `push` is fine as a hashmaps have unique keys.
|
||||||
|
// To ensure this uniqueness, we only allow hashmaps with strings as
|
||||||
|
// keys and not keys which implement `Into<String>` or `ToString`.
|
||||||
|
record.push(k, v.into_value(span));
|
||||||
|
}
|
||||||
|
Value::record(record, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nu Types
|
||||||
|
|
||||||
|
impl IntoValue for Value {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
self.with_span(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use this type for all the `into_value` methods that types implement but return a Result
|
||||||
|
/// A trait for trying to convert a value into a `Value`.
|
||||||
|
///
|
||||||
|
/// Types like streams may fail while collecting the `Value`,
|
||||||
|
/// for these types it is useful to implement a fallible variant.
|
||||||
|
///
|
||||||
|
/// This conversion is fallible, for infallible conversions use [`IntoValue`].
|
||||||
|
/// All types that implement `IntoValue` will automatically implement this trait.
|
||||||
|
pub trait TryIntoValue: Sized {
|
||||||
|
// TODO: instead of ShellError, maybe we could have a IntoValueError that implements Into<ShellError>
|
||||||
|
/// Tries to convert the given value into a `Value`.
|
||||||
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TryIntoValue for T
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError> {
|
||||||
|
Ok(self.into_value(span))
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,10 @@ mod filesize;
|
||||||
mod from;
|
mod from;
|
||||||
mod from_value;
|
mod from_value;
|
||||||
mod glob;
|
mod glob;
|
||||||
|
mod into_value;
|
||||||
mod range;
|
mod range;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_derive;
|
||||||
|
|
||||||
pub mod record;
|
pub mod record;
|
||||||
pub use custom_value::CustomValue;
|
pub use custom_value::CustomValue;
|
||||||
|
@ -12,6 +15,7 @@ pub use duration::*;
|
||||||
pub use filesize::*;
|
pub use filesize::*;
|
||||||
pub use from_value::FromValue;
|
pub use from_value::FromValue;
|
||||||
pub use glob::*;
|
pub use glob::*;
|
||||||
|
pub use into_value::{IntoValue, TryIntoValue};
|
||||||
pub use range::{FloatRange, IntRange, Range};
|
pub use range::{FloatRange, IntRange, Range};
|
||||||
pub use record::Record;
|
pub use record::Record;
|
||||||
|
|
||||||
|
@ -1089,7 +1093,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1126,7 +1130,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CantFindColumn {
|
Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1140,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::CantFindColumn {
|
_ => Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -1237,7 +1241,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1262,7 +1266,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1342,7 +1346,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1355,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1364,7 +1368,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1373,7 +1377,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1427,7 +1431,7 @@ impl Value {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1435,7 +1439,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1447,7 +1451,7 @@ impl Value {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1455,7 +1459,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -1504,7 +1508,7 @@ impl Value {
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1512,7 +1516,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1526,7 +1530,7 @@ impl Value {
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1534,7 +1538,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
386
crates/nu-protocol/src/value/test_derive.rs
Normal file
386
crates/nu-protocol/src/value/test_derive.rs
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
use crate::{record, FromValue, IntoValue, Record, Span, Value};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Make nu_protocol available in this namespace, consumers of this crate will
|
||||||
|
// have this without such an export.
|
||||||
|
// The derive macro fully qualifies paths to "nu_protocol".
|
||||||
|
use crate as nu_protocol;
|
||||||
|
|
||||||
|
trait IntoTestValue {
|
||||||
|
fn into_test_value(self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoTestValue for T
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_test_value(self) -> Value {
|
||||||
|
self.into_value(Span::test_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct NamedFieldsStruct<T>
|
||||||
|
where
|
||||||
|
T: IntoValue + FromValue,
|
||||||
|
{
|
||||||
|
array: [u16; 4],
|
||||||
|
bool: bool,
|
||||||
|
char: char,
|
||||||
|
f32: f32,
|
||||||
|
f64: f64,
|
||||||
|
i8: i8,
|
||||||
|
i16: i16,
|
||||||
|
i32: i32,
|
||||||
|
i64: i64,
|
||||||
|
isize: isize,
|
||||||
|
u16: u16,
|
||||||
|
u32: u32,
|
||||||
|
unit: (),
|
||||||
|
tuple: (u32, bool),
|
||||||
|
some: Option<u32>,
|
||||||
|
none: Option<u32>,
|
||||||
|
vec: Vec<T>,
|
||||||
|
string: String,
|
||||||
|
hashmap: HashMap<String, u32>,
|
||||||
|
nested: Nestee,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct Nestee {
|
||||||
|
u32: u32,
|
||||||
|
some: Option<u32>,
|
||||||
|
none: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamedFieldsStruct<u32> {
|
||||||
|
fn make() -> Self {
|
||||||
|
Self {
|
||||||
|
array: [1, 2, 3, 4],
|
||||||
|
bool: true,
|
||||||
|
char: 'a',
|
||||||
|
f32: std::f32::consts::PI,
|
||||||
|
f64: std::f64::consts::E,
|
||||||
|
i8: 127,
|
||||||
|
i16: -32768,
|
||||||
|
i32: 2147483647,
|
||||||
|
i64: -9223372036854775808,
|
||||||
|
isize: 2,
|
||||||
|
u16: 65535,
|
||||||
|
u32: 4294967295,
|
||||||
|
unit: (),
|
||||||
|
tuple: (1, true),
|
||||||
|
some: Some(123),
|
||||||
|
none: None,
|
||||||
|
vec: vec![10, 20, 30],
|
||||||
|
string: "string".to_string(),
|
||||||
|
hashmap: HashMap::from_iter([("a".to_string(), 10), ("b".to_string(), 20)]),
|
||||||
|
nested: Nestee {
|
||||||
|
u32: 3,
|
||||||
|
some: Some(42),
|
||||||
|
none: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_record(record! {
|
||||||
|
"array" => Value::test_list(vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(3),
|
||||||
|
Value::test_int(4)
|
||||||
|
]),
|
||||||
|
"bool" => Value::test_bool(true),
|
||||||
|
"char" => Value::test_string('a'),
|
||||||
|
"f32" => Value::test_float(std::f32::consts::PI.into()),
|
||||||
|
"f64" => Value::test_float(std::f64::consts::E),
|
||||||
|
"i8" => Value::test_int(127),
|
||||||
|
"i16" => Value::test_int(-32768),
|
||||||
|
"i32" => Value::test_int(2147483647),
|
||||||
|
"i64" => Value::test_int(-9223372036854775808),
|
||||||
|
"isize" => Value::test_int(2),
|
||||||
|
"u16" => Value::test_int(65535),
|
||||||
|
"u32" => Value::test_int(4294967295),
|
||||||
|
"unit" => Value::test_nothing(),
|
||||||
|
"tuple" => Value::test_list(vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_bool(true)
|
||||||
|
]),
|
||||||
|
"some" => Value::test_int(123),
|
||||||
|
"none" => Value::test_nothing(),
|
||||||
|
"vec" => Value::test_list(vec![
|
||||||
|
Value::test_int(10),
|
||||||
|
Value::test_int(20),
|
||||||
|
Value::test_int(30)
|
||||||
|
]),
|
||||||
|
"string" => Value::test_string("string"),
|
||||||
|
"hashmap" => Value::test_record(record! {
|
||||||
|
"a" => Value::test_int(10),
|
||||||
|
"b" => Value::test_int(20)
|
||||||
|
}),
|
||||||
|
"nested" => Value::test_record(record! {
|
||||||
|
"u32" => Value::test_int(3),
|
||||||
|
"some" => Value::test_int(42),
|
||||||
|
"none" => Value::test_nothing(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_into_value() {
|
||||||
|
let expected = NamedFieldsStruct::value();
|
||||||
|
let actual = NamedFieldsStruct::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_from_value() {
|
||||||
|
let expected = NamedFieldsStruct::make();
|
||||||
|
let actual = NamedFieldsStruct::from_value(NamedFieldsStruct::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_roundtrip() {
|
||||||
|
let expected = NamedFieldsStruct::make();
|
||||||
|
let actual =
|
||||||
|
NamedFieldsStruct::from_value(NamedFieldsStruct::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = NamedFieldsStruct::value();
|
||||||
|
let actual = NamedFieldsStruct::<u32>::from_value(NamedFieldsStruct::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_missing_value() {
|
||||||
|
let value = Value::test_record(Record::new());
|
||||||
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct UnnamedFieldsStruct<T>(u32, String, T)
|
||||||
|
where
|
||||||
|
T: IntoValue + FromValue;
|
||||||
|
|
||||||
|
impl UnnamedFieldsStruct<f64> {
|
||||||
|
fn make() -> Self {
|
||||||
|
UnnamedFieldsStruct(420, "Hello, tuple!".to_string(), 33.33)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_int(420),
|
||||||
|
Value::test_string("Hello, tuple!"),
|
||||||
|
Value::test_float(33.33),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_into_value() {
|
||||||
|
let expected = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_from_value() {
|
||||||
|
let expected = UnnamedFieldsStruct::make();
|
||||||
|
let value = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::from_value(value).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_roundtrip() {
|
||||||
|
let expected = UnnamedFieldsStruct::make();
|
||||||
|
let actual =
|
||||||
|
UnnamedFieldsStruct::from_value(UnnamedFieldsStruct::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::<f64>::from_value(UnnamedFieldsStruct::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_missing_value() {
|
||||||
|
let value = Value::test_list(vec![]);
|
||||||
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct UnitStruct;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_into_value() {
|
||||||
|
let expected = Value::test_nothing();
|
||||||
|
let actual = UnitStruct.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_from_value() {
|
||||||
|
let expected = UnitStruct;
|
||||||
|
let actual = UnitStruct::from_value(Value::test_nothing()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_roundtrip() {
|
||||||
|
let expected = UnitStruct;
|
||||||
|
let actual = UnitStruct::from_value(UnitStruct.into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = Value::test_nothing();
|
||||||
|
let actual = UnitStruct::from_value(Value::test_nothing())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
enum Enum {
|
||||||
|
AlphaOne,
|
||||||
|
BetaTwo,
|
||||||
|
CharlieThree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Enum {
|
||||||
|
fn make() -> [Self; 3] {
|
||||||
|
[Enum::AlphaOne, Enum::BetaTwo, Enum::CharlieThree]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_string("alpha_one"),
|
||||||
|
Value::test_string("beta_two"),
|
||||||
|
Value::test_string("charlie_three"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_into_value() {
|
||||||
|
let expected = Enum::value();
|
||||||
|
let actual = Enum::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_from_value() {
|
||||||
|
let expected = Enum::make();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_roundtrip() {
|
||||||
|
let expected = Enum::make();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = Enum::value();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_unknown_variant() {
|
||||||
|
let value = Value::test_string("delta_four");
|
||||||
|
let res = Enum::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res = Enum::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the `Enum` from before but with all possible `rename_all` variants.
|
||||||
|
macro_rules! enum_rename_all {
|
||||||
|
($($ident:ident: $case:literal => [$a1:literal, $b2:literal, $c3:literal]),*) => {
|
||||||
|
$(
|
||||||
|
#[derive(Debug, PartialEq, IntoValue, FromValue)]
|
||||||
|
#[nu_value(rename_all = $case)]
|
||||||
|
enum $ident {
|
||||||
|
AlphaOne,
|
||||||
|
BetaTwo,
|
||||||
|
CharlieThree
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $ident {
|
||||||
|
fn make() -> [Self; 3] {
|
||||||
|
[Self::AlphaOne, Self::BetaTwo, Self::CharlieThree]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_string($a1),
|
||||||
|
Value::test_string($b2),
|
||||||
|
Value::test_string($c3),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_rename_all_into_value() {$({
|
||||||
|
let expected = $ident::value();
|
||||||
|
let actual = $ident::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
})*}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_rename_all_from_value() {$({
|
||||||
|
let expected = $ident::make();
|
||||||
|
let actual = <[$ident; 3]>::from_value($ident::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
})*}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_rename_all! {
|
||||||
|
Upper: "UPPER CASE" => ["ALPHA ONE", "BETA TWO", "CHARLIE THREE"],
|
||||||
|
Lower: "lower case" => ["alpha one", "beta two", "charlie three"],
|
||||||
|
Title: "Title Case" => ["Alpha One", "Beta Two", "Charlie Three"],
|
||||||
|
Camel: "camelCase" => ["alphaOne", "betaTwo", "charlieThree"],
|
||||||
|
Pascal: "PascalCase" => ["AlphaOne", "BetaTwo", "CharlieThree"],
|
||||||
|
Snake: "snake_case" => ["alpha_one", "beta_two", "charlie_three"],
|
||||||
|
UpperSnake: "UPPER_SNAKE_CASE" => ["ALPHA_ONE", "BETA_TWO", "CHARLIE_THREE"],
|
||||||
|
Kebab: "kebab-case" => ["alpha-one", "beta-two", "charlie-three"],
|
||||||
|
Cobol: "COBOL-CASE" => ["ALPHA-ONE", "BETA-TWO", "CHARLIE-THREE"],
|
||||||
|
Train: "Train-Case" => ["Alpha-One", "Beta-Two", "Charlie-Three"],
|
||||||
|
Flat: "flatcase" => ["alphaone", "betatwo", "charliethree"],
|
||||||
|
UpperFlat: "UPPERFLATCASE" => ["ALPHAONE", "BETATWO", "CHARLIETHREE"]
|
||||||
|
}
|
|
@ -658,7 +658,7 @@ def build-command-page [command: record] {
|
||||||
$" > ($example.example | nu-highlight)"
|
$" > ($example.example | nu-highlight)"
|
||||||
(if not ($example.result | is-empty) {
|
(if not ($example.result | is-empty) {
|
||||||
$example.result
|
$example.result
|
||||||
| table
|
| table -e
|
||||||
| to text
|
| to text
|
||||||
| if ($example.result | describe) == "binary" { str join } else { lines }
|
| if ($example.result | describe) == "binary" { str join } else { lines }
|
||||||
| each {|line|
|
| each {|line|
|
||||||
|
@ -771,6 +771,11 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https
|
||||||
|
|
||||||
let modules = (try { modules $target_item --find $find })
|
let modules = (try { modules $target_item --find $find })
|
||||||
if not ($modules | is-empty) { return $modules }
|
if not ($modules | is-empty) { return $modules }
|
||||||
|
|
||||||
|
if ($find | is-not-empty) {
|
||||||
|
print -e $"No help results found mentioning: ($find)"
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
let span = (metadata $item | get span)
|
let span = (metadata $item | get span)
|
||||||
error make {
|
error make {
|
||||||
|
|
|
@ -161,10 +161,7 @@ export def bench [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# print a banner for nushell, with information about the project
|
# Print a banner for nushell with information about the project
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# an example can be found in [this asciinema recording](https://asciinema.org/a/566513)
|
|
||||||
export def banner [] {
|
export def banner [] {
|
||||||
let dt = (datetime-diff (date now) 2019-05-10T09:59:12-07:00)
|
let dt = (datetime-diff (date now) 2019-05-10T09:59:12-07:00)
|
||||||
$"(ansi green) __ ,(ansi reset)
|
$"(ansi green) __ ,(ansi reset)
|
||||||
|
|
|
@ -259,9 +259,7 @@ fn draw_table(
|
||||||
table.with(width_ctrl);
|
table.with(width_ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tablewidth = table.total_width();
|
table_to_string(table, termwidth)
|
||||||
|
|
||||||
table_to_string(table, tablewidth, termwidth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_indent(table: &mut Table, left: usize, right: usize) {
|
fn set_indent(table: &mut Table, left: usize, right: usize) {
|
||||||
|
@ -289,7 +287,9 @@ fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_to_string(table: Table, total_width: usize, termwidth: usize) -> Option<String> {
|
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
||||||
|
let total_width = table.total_width();
|
||||||
|
|
||||||
if total_width > termwidth {
|
if total_width > termwidth {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -318,6 +318,7 @@ impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for
|
||||||
dim: &mut CompleteDimensionVecRecords<'_>,
|
dim: &mut CompleteDimensionVecRecords<'_>,
|
||||||
) {
|
) {
|
||||||
let total_width = get_total_width2(&self.width, cfg);
|
let total_width = get_total_width2(&self.width, cfg);
|
||||||
|
|
||||||
if total_width > self.max {
|
if total_width > self.max {
|
||||||
TableTrim {
|
TableTrim {
|
||||||
max: self.max,
|
max: self.max,
|
||||||
|
|
|
@ -289,6 +289,8 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef<str>, with_std: bool) -> O
|
||||||
if !with_std {
|
if !with_std {
|
||||||
command.arg("--no-std-lib");
|
command.arg("--no-std-lib");
|
||||||
}
|
}
|
||||||
|
// Use plain errors to help make error text matching more consistent
|
||||||
|
command.args(["--error-style", "plain"]);
|
||||||
command
|
command
|
||||||
.arg(format!("-c {}", escape_quote_string(&commands)))
|
.arg(format!("-c {}", escape_quote_string(&commands)))
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
@ -375,6 +377,8 @@ where
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.arg("--commands")
|
.arg("--commands")
|
||||||
.arg(command)
|
.arg(command)
|
||||||
|
// Use plain errors to help make error text matching more consistent
|
||||||
|
.args(["--error-style", "plain"])
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
.arg(temp_config_file)
|
.arg(temp_config_file)
|
||||||
.arg("--env-config")
|
.arg("--env-config")
|
||||||
|
|
|
@ -77,6 +77,7 @@ $env.ENV_CONVERSIONS = {
|
||||||
# The default for this is $nu.default-config-dir/scripts
|
# The default for this is $nu.default-config-dir/scripts
|
||||||
$env.NU_LIB_DIRS = [
|
$env.NU_LIB_DIRS = [
|
||||||
($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
|
($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
|
||||||
|
($nu.data-dir | path join 'completions') # default home for nushell completions
|
||||||
]
|
]
|
||||||
|
|
||||||
# Directories to search for plugin binaries when calling register
|
# Directories to search for plugin binaries when calling register
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl CustomValue for CoolCustomValue {
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CantFindColumn {
|
Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name,
|
col_name: column_name,
|
||||||
span: path_span,
|
span: Some(path_span),
|
||||||
src_span: self_span,
|
src_span: self_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ bench = false
|
||||||
nu-plugin = { path = "../nu-plugin", version = "0.94.3" }
|
nu-plugin = { path = "../nu-plugin", version = "0.94.3" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||||
|
|
||||||
git2 = "0.18"
|
git2 = "0.19"
|
||||||
|
|
|
@ -35,6 +35,7 @@ impl PluginCommand for ToNu {
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
.switch("tail", "shows tail rows", Some('t'))
|
.switch("tail", "shows tail rows", Some('t'))
|
||||||
|
.switch("index", "add an index column", Some('i'))
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Custom("expression".into()), Type::Any),
|
(Type::Custom("expression".into()), Type::Any),
|
||||||
(Type::Custom("dataframe".into()), Type::table()),
|
(Type::Custom("dataframe".into()), Type::table()),
|
||||||
|
@ -62,18 +63,18 @@ impl PluginCommand for ToNu {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Shows head rows from dataframe",
|
description: "Shows head rows from dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | polars into-df | polars into-nu",
|
example: "[[a b]; [1 2] [3 4]] | polars into-df | polars into-nu --index",
|
||||||
result: Some(Value::list(vec![rec_1, rec_2], Span::test_data())),
|
result: Some(Value::list(vec![rec_1, rec_2], Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shows tail rows from dataframe",
|
description: "Shows tail rows from dataframe",
|
||||||
example:
|
example:
|
||||||
"[[a b]; [1 2] [5 6] [3 4]] | polars into-df | polars into-nu --tail --rows 1",
|
"[[a b]; [1 2] [5 6] [3 4]] | polars into-df | polars into-nu --tail --rows 1 --index",
|
||||||
result: Some(Value::list(vec![rec_3], Span::test_data())),
|
result: Some(Value::list(vec![rec_3], Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert a col expression into a nushell value",
|
description: "Convert a col expression into a nushell value",
|
||||||
example: "polars col a | polars into-nu",
|
example: "polars col a | polars into-nu --index",
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_record(record! {
|
||||||
"expr" => Value::test_string("column"),
|
"expr" => Value::test_string("column"),
|
||||||
"value" => Value::test_string("a"),
|
"value" => Value::test_string("a"),
|
||||||
|
@ -106,17 +107,18 @@ fn dataframe_command(
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let rows: Option<usize> = call.get_flag("rows")?;
|
let rows: Option<usize> = call.get_flag("rows")?;
|
||||||
let tail: bool = call.has_flag("tail")?;
|
let tail: bool = call.has_flag("tail")?;
|
||||||
|
let index: bool = call.has_flag("index")?;
|
||||||
|
|
||||||
let df = NuDataFrame::try_from_value_coerce(plugin, &input, call.head)?;
|
let df = NuDataFrame::try_from_value_coerce(plugin, &input, call.head)?;
|
||||||
|
|
||||||
let values = if tail {
|
let values = if tail {
|
||||||
df.tail(rows, call.head)?
|
df.tail(rows, index, call.head)?
|
||||||
} else {
|
} else {
|
||||||
// if rows is specified, return those rows, otherwise return everything
|
// if rows is specified, return those rows, otherwise return everything
|
||||||
if rows.is_some() {
|
if rows.is_some() {
|
||||||
df.head(rows, call.head)?
|
df.head(rows, index, call.head)?
|
||||||
} else {
|
} else {
|
||||||
df.head(Some(df.height()), call.head)?
|
df.head(Some(df.height()), index, call.head)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -326,22 +326,22 @@ impl NuDataFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print is made out a head and if the dataframe is too large, then a tail
|
// Print is made out a head and if the dataframe is too large, then a tail
|
||||||
pub fn print(&self, span: Span) -> Result<Vec<Value>, ShellError> {
|
pub fn print(&self, include_index: bool, span: Span) -> Result<Vec<Value>, ShellError> {
|
||||||
let df = &self.df;
|
let df = &self.df;
|
||||||
let size: usize = 20;
|
let size: usize = 20;
|
||||||
|
|
||||||
if df.height() > size {
|
if df.height() > size {
|
||||||
let sample_size = size / 2;
|
let sample_size = size / 2;
|
||||||
let mut values = self.head(Some(sample_size), span)?;
|
let mut values = self.head(Some(sample_size), include_index, span)?;
|
||||||
conversion::add_separator(&mut values, df, self.has_index(), span);
|
conversion::add_separator(&mut values, df, self.has_index(), span);
|
||||||
let remaining = df.height() - sample_size;
|
let remaining = df.height() - sample_size;
|
||||||
let tail_size = remaining.min(sample_size);
|
let tail_size = remaining.min(sample_size);
|
||||||
let mut tail_values = self.tail(Some(tail_size), span)?;
|
let mut tail_values = self.tail(Some(tail_size), include_index, span)?;
|
||||||
values.append(&mut tail_values);
|
values.append(&mut tail_values);
|
||||||
|
|
||||||
Ok(values)
|
Ok(values)
|
||||||
} else {
|
} else {
|
||||||
Ok(self.head(Some(size), span)?)
|
Ok(self.head(Some(size), include_index, span)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,26 +349,38 @@ impl NuDataFrame {
|
||||||
self.df.height()
|
self.df.height()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn head(&self, rows: Option<usize>, span: Span) -> Result<Vec<Value>, ShellError> {
|
pub fn head(
|
||||||
|
&self,
|
||||||
|
rows: Option<usize>,
|
||||||
|
include_index: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
let to_row = rows.unwrap_or(5);
|
let to_row = rows.unwrap_or(5);
|
||||||
let values = self.to_rows(0, to_row, span)?;
|
let values = self.to_rows(0, to_row, include_index, span)?;
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tail(&self, rows: Option<usize>, span: Span) -> Result<Vec<Value>, ShellError> {
|
pub fn tail(
|
||||||
|
&self,
|
||||||
|
rows: Option<usize>,
|
||||||
|
include_index: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
let df = &self.df;
|
let df = &self.df;
|
||||||
let to_row = df.height();
|
let to_row = df.height();
|
||||||
let size = rows.unwrap_or(DEFAULT_ROWS);
|
let size = rows.unwrap_or(DEFAULT_ROWS);
|
||||||
let from_row = to_row.saturating_sub(size);
|
let from_row = to_row.saturating_sub(size);
|
||||||
|
|
||||||
let values = self.to_rows(from_row, to_row, span)?;
|
let values = self.to_rows(from_row, to_row, include_index, span)?;
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the dataframe to a nushell list of values
|
||||||
pub fn to_rows(
|
pub fn to_rows(
|
||||||
&self,
|
&self,
|
||||||
from_row: usize,
|
from_row: usize,
|
||||||
to_row: usize,
|
to_row: usize,
|
||||||
|
include_index: bool,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Vec<Value>, ShellError> {
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
let df = &self.df;
|
let df = &self.df;
|
||||||
|
@ -400,7 +412,7 @@ impl NuDataFrame {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
|
|
||||||
if !has_index {
|
if !has_index && include_index {
|
||||||
record.push("index", Value::int((i + from_row) as i64, span));
|
record.push("index", Value::int((i + from_row) as i64, span));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,7 +614,7 @@ impl CustomValueSupport for NuDataFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_value(self, span: Span) -> Result<Value, ShellError> {
|
fn base_value(self, span: Span) -> Result<Value, ShellError> {
|
||||||
let vals = self.print(span)?;
|
let vals = self.print(true, span)?;
|
||||||
Ok(Value::list(vals, span))
|
Ok(Value::list(vals, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,45 +63,16 @@ fn compute_with_value(
|
||||||
op: Span,
|
op: Span,
|
||||||
right: &Value,
|
right: &Value,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let rhs_span = right.span();
|
let rhs = NuExpression::try_from_value(plugin, right)?;
|
||||||
match right {
|
with_operator(
|
||||||
Value::Custom { val: rhs, .. } => {
|
(plugin, engine),
|
||||||
let rhs = rhs.as_any().downcast_ref::<NuExpression>().ok_or_else(|| {
|
operator,
|
||||||
ShellError::TypeMismatch {
|
left,
|
||||||
err_message: "Right hand side not a dataframe expression".into(),
|
&rhs,
|
||||||
span: rhs_span,
|
lhs_span,
|
||||||
}
|
right.span(),
|
||||||
})?;
|
op,
|
||||||
|
)
|
||||||
match rhs.as_ref() {
|
|
||||||
polars::prelude::Expr::Literal(..) => with_operator(
|
|
||||||
(plugin, engine),
|
|
||||||
operator,
|
|
||||||
left,
|
|
||||||
rhs,
|
|
||||||
lhs_span,
|
|
||||||
right.span(),
|
|
||||||
op,
|
|
||||||
),
|
|
||||||
_ => Err(ShellError::TypeMismatch {
|
|
||||||
err_message: "Only literal expressions or number".into(),
|
|
||||||
span: right.span(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let rhs = NuExpression::try_from_value(plugin, right)?;
|
|
||||||
with_operator(
|
|
||||||
(plugin, engine),
|
|
||||||
operator,
|
|
||||||
left,
|
|
||||||
&rhs,
|
|
||||||
lhs_span,
|
|
||||||
right.span(),
|
|
||||||
op,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_operator(
|
fn with_operator(
|
||||||
|
|
|
@ -29,8 +29,10 @@ pub(crate) fn gather_commandline_args() -> (Vec<String>, String, Vec<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let flag_value = match arg.as_ref() {
|
let flag_value = match arg.as_ref() {
|
||||||
"--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" | "--config"
|
"--commands" | "-c" | "--table-mode" | "-m" | "--error-style" | "-e" | "--execute"
|
||||||
| "--env-config" | "-I" | "ide-ast" => args.next().map(|a| escape_quote_string(&a)),
|
| "--config" | "--env-config" | "-I" | "ide-ast" => {
|
||||||
|
args.next().map(|a| escape_quote_string(&a))
|
||||||
|
}
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
||||||
"--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin"
|
"--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin"
|
||||||
|
@ -102,6 +104,8 @@ pub(crate) fn parse_commandline_args(
|
||||||
let execute = call.get_flag_expr("execute");
|
let execute = call.get_flag_expr("execute");
|
||||||
let table_mode: Option<Value> =
|
let table_mode: Option<Value> =
|
||||||
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
||||||
|
let error_style: Option<Value> =
|
||||||
|
call.get_flag(engine_state, &mut stack, "error-style")?;
|
||||||
let no_newline = call.get_named_arg("no-newline");
|
let no_newline = call.get_named_arg("no-newline");
|
||||||
|
|
||||||
// ide flags
|
// ide flags
|
||||||
|
@ -245,6 +249,7 @@ pub(crate) fn parse_commandline_args(
|
||||||
ide_check,
|
ide_check,
|
||||||
ide_ast,
|
ide_ast,
|
||||||
table_mode,
|
table_mode,
|
||||||
|
error_style,
|
||||||
no_newline,
|
no_newline,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -278,6 +283,7 @@ pub(crate) struct NushellCliArgs {
|
||||||
pub(crate) log_exclude: Option<Vec<Spanned<String>>>,
|
pub(crate) log_exclude: Option<Vec<Spanned<String>>>,
|
||||||
pub(crate) execute: Option<Spanned<String>>,
|
pub(crate) execute: Option<Spanned<String>>,
|
||||||
pub(crate) table_mode: Option<Value>,
|
pub(crate) table_mode: Option<Value>,
|
||||||
|
pub(crate) error_style: Option<Value>,
|
||||||
pub(crate) no_newline: Option<Spanned<String>>,
|
pub(crate) no_newline: Option<Spanned<String>>,
|
||||||
pub(crate) include_path: Option<Spanned<String>>,
|
pub(crate) include_path: Option<Spanned<String>>,
|
||||||
pub(crate) lsp: bool,
|
pub(crate) lsp: bool,
|
||||||
|
@ -325,6 +331,12 @@ impl Command for Nu {
|
||||||
"the table mode to use. rounded is default.",
|
"the table mode to use. rounded is default.",
|
||||||
Some('m'),
|
Some('m'),
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"error-style",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the error style to use (fancy or plain). default: fancy",
|
||||||
|
None,
|
||||||
|
)
|
||||||
.switch("no-newline", "print the result for --commands(-c) without a newline", None)
|
.switch("no-newline", "print the result for --commands(-c) without a newline", None)
|
||||||
.switch(
|
.switch(
|
||||||
"no-config-file",
|
"no-config-file",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user