Merge branch 'main' into path-migration-3

This commit is contained in:
Ian Manske 2024-07-31 22:04:14 -07:00
commit d198b3b66c
239 changed files with 4108 additions and 1615 deletions

View File

@ -161,7 +161,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release # REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo # Create a release only in nushell/nightly repo
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v2.0.6 uses: softprops/action-gh-release@v2.0.8
if: ${{ startsWith(github.repository, 'nushell/nightly') }} if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with: with:
prerelease: true prerelease: true

View File

@ -161,8 +161,12 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
let releaseStem = $'($bin)-($version)-($target)' let releaseStem = $'($bin)-($version)-($target)'
print $'(char nl)Download less related stuffs...'; hr-line print $'(char nl)Download less related stuffs...'; hr-line
# todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it?
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt # the below was renamed because it was failing to download for darren. it should work but it wasn't
# todo: maybe we should get rid of this aria2c dependency and just use http get?
#aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt
# Create Windows msi release package # Create Windows msi release package
if (get-env _EXTRA_) == 'msi' { if (get-env _EXTRA_) == 'msi' {

View File

@ -91,7 +91,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release # REF: https://github.com/marketplace/actions/gh-release
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v2.0.6 uses: softprops/action-gh-release@v2.0.8
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
draft: true draft: true

View File

@ -10,4 +10,4 @@ jobs:
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@v1.23.2 uses: crate-ci/typos@v1.23.5

149
Cargo.lock generated
View File

@ -1219,24 +1219,24 @@ dependencies = [
] ]
[[package]] [[package]]
name = "dirs-next" name = "dirs"
version = "2.0.0" version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [ dependencies = [
"cfg-if", "dirs-sys",
"dirs-sys-next",
] ]
[[package]] [[package]]
name = "dirs-sys-next" name = "dirs-sys"
version = "0.1.2" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext",
"redox_users", "redox_users",
"winapi", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@ -2868,12 +2868,12 @@ dependencies = [
[[package]] [[package]]
name = "nu" name = "nu"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"assert_cmd", "assert_cmd",
"crossterm", "crossterm",
"ctrlc", "ctrlc",
"dirs-next", "dirs",
"log", "log",
"miette", "miette",
"mimalloc", "mimalloc",
@ -2913,16 +2913,16 @@ dependencies = [
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.0" version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
name = "nu-cli" name = "nu-cli"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossterm", "crossterm",
@ -2957,7 +2957,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-base" name = "nu-cmd-base"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"miette", "miette",
@ -2969,7 +2969,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-extra" name = "nu-cmd-extra"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"fancy-regex", "fancy-regex",
"heck 0.5.0", "heck 0.5.0",
@ -2994,7 +2994,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"nu-engine", "nu-engine",
@ -3006,7 +3006,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-plugin" name = "nu-cmd-plugin"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"nu-engine", "nu-engine",
@ -3017,7 +3017,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-color-config" name = "nu-color-config"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"nu-engine", "nu-engine",
@ -3029,7 +3029,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-command" name = "nu-command"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"alphanumeric-sort", "alphanumeric-sort",
"base64 0.22.1", "base64 0.22.1",
@ -3047,7 +3047,7 @@ dependencies = [
"deunicode", "deunicode",
"dialoguer", "dialoguer",
"digest", "digest",
"dirs-next", "dirs",
"dtparse", "dtparse",
"encoding_rs", "encoding_rs",
"fancy-regex", "fancy-regex",
@ -3139,7 +3139,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-derive-value" name = "nu-derive-value"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro-error", "proc-macro-error",
@ -3150,7 +3150,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-engine" name = "nu-engine"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"log", "log",
"nu-glob", "nu-glob",
@ -3161,7 +3161,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-explore" name = "nu-explore"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"ansi-str", "ansi-str",
"anyhow", "anyhow",
@ -3186,16 +3186,19 @@ dependencies = [
[[package]] [[package]]
name = "nu-glob" name = "nu-glob"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"doc-comment", "doc-comment",
] ]
[[package]] [[package]]
name = "nu-json" name = "nu-json"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"fancy-regex",
"linked-hash-map", "linked-hash-map",
"nu-path",
"nu-test-support",
"num-traits", "num-traits",
"serde", "serde",
"serde_json", "serde_json",
@ -3203,7 +3206,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-lsp" name = "nu-lsp"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"assert-json-diff", "assert-json-diff",
"crossbeam-channel", "crossbeam-channel",
@ -3224,7 +3227,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-parser" name = "nu-parser"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"bytesize", "bytesize",
"chrono", "chrono",
@ -3240,16 +3243,16 @@ dependencies = [
[[package]] [[package]]
name = "nu-path" name = "nu-path"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"dirs-next", "dirs",
"omnipath", "omnipath",
"pwd", "pwd",
] ]
[[package]] [[package]]
name = "nu-plugin" name = "nu-plugin"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"log", "log",
"nix", "nix",
@ -3265,7 +3268,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-core" name = "nu-plugin-core"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"interprocess", "interprocess",
"log", "log",
@ -3279,7 +3282,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-engine" name = "nu-plugin-engine"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"log", "log",
"nu-engine", "nu-engine",
@ -3295,7 +3298,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-protocol" name = "nu-plugin-protocol"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"bincode", "bincode",
"nu-protocol", "nu-protocol",
@ -3307,7 +3310,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-test-support" name = "nu-plugin-test-support"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"nu-cmd-lang", "nu-cmd-lang",
@ -3325,7 +3328,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-pretty-hex" name = "nu-pretty-hex"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"heapless", "heapless",
"nu-ansi-term", "nu-ansi-term",
@ -3334,13 +3337,15 @@ dependencies = [
[[package]] [[package]]
name = "nu-protocol" name = "nu-protocol"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"brotli", "brotli",
"byte-unit", "byte-unit",
"chrono", "chrono",
"chrono-humanize", "chrono-humanize",
"convert_case", "convert_case",
"dirs",
"dirs-sys",
"fancy-regex", "fancy-regex",
"indexmap", "indexmap",
"log", "log",
@ -3364,11 +3369,12 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"typetag", "typetag",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]
name = "nu-std" name = "nu-std"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"log", "log",
"miette", "miette",
@ -3379,7 +3385,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-system" name = "nu-system"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"itertools 0.12.1", "itertools 0.12.1",
@ -3397,7 +3403,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-table" name = "nu-table"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"fancy-regex", "fancy-regex",
"nu-ansi-term", "nu-ansi-term",
@ -3411,7 +3417,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-term-grid" name = "nu-term-grid"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-utils", "nu-utils",
"unicode-width", "unicode-width",
@ -3419,7 +3425,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-test-support" name = "nu-test-support"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-glob", "nu-glob",
"nu-path", "nu-path",
@ -3431,7 +3437,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-utils" name = "nu-utils"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"crossterm_winapi", "crossterm_winapi",
"log", "log",
@ -3457,7 +3463,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_example" name = "nu_plugin_example"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-cmd-lang", "nu-cmd-lang",
"nu-plugin", "nu-plugin",
@ -3467,7 +3473,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_formats" name = "nu_plugin_formats"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"eml-parser", "eml-parser",
"ical", "ical",
@ -3480,7 +3486,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_gstat" name = "nu_plugin_gstat"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"git2", "git2",
"nu-plugin", "nu-plugin",
@ -3489,7 +3495,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_inc" name = "nu_plugin_inc"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"nu-plugin", "nu-plugin",
"nu-protocol", "nu-protocol",
@ -3498,7 +3504,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_polars" name = "nu_plugin_polars"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz 0.9.0", "chrono-tz 0.9.0",
@ -3532,7 +3538,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_query" name = "nu_plugin_query"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"gjson", "gjson",
"nu-plugin", "nu-plugin",
@ -3547,7 +3553,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_stress_internals" name = "nu_plugin_stress_internals"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"interprocess", "interprocess",
"serde", "serde",
@ -3673,7 +3679,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "nuon" name = "nuon"
version = "0.95.1" version = "0.96.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"fancy-regex", "fancy-regex",
@ -3770,9 +3776,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "open" name = "open"
version = "5.2.0" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
dependencies = [ dependencies = [
"is-wsl", "is-wsl",
"libc", "libc",
@ -3781,9 +3787,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.64" version = "0.10.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"cfg-if", "cfg-if",
@ -3822,9 +3828,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.102" version = "0.9.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -3833,6 +3839,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "ordered-multimap" name = "ordered-multimap"
version = "0.7.3" version = "0.7.3"
@ -4992,8 +5004,9 @@ dependencies = [
[[package]] [[package]]
name = "reedline" name = "reedline"
version = "0.32.0" version = "0.33.0"
source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8c676a3f3814a23c6a0fc9dff6b6c35b2e04df8134aae6f3929cc34de21a53"
dependencies = [ dependencies = [
"arboard", "arboard",
"chrono", "chrono",
@ -5207,9 +5220,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed" name = "rust-embed"
version = "8.4.0" version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
dependencies = [ dependencies = [
"rust-embed-impl", "rust-embed-impl",
"rust-embed-utils", "rust-embed-utils",
@ -5576,9 +5589,9 @@ dependencies = [
[[package]] [[package]]
name = "shadow-rs" name = "shadow-rs"
version = "0.29.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a600f795d0894cda22235b44eea4b85c2a35b405f65523645ac8e35b306817a" checksum = "d253e54681d4be0161e965db57974ae642a0b6aaeb18a999424c4dab062be8c5"
dependencies = [ dependencies = [
"const_format", "const_format",
"is_debug", "is_debug",
@ -5653,9 +5666,9 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
[[package]] [[package]]
name = "simplelog" name = "simplelog"
@ -6629,9 +6642,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.9.1" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT" license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.77.2" rust-version = "1.78.0"
version = "0.95.1" version = "0.96.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -83,7 +83,8 @@ ctrlc = "3.4"
deunicode = "1.6.0" deunicode = "1.6.0"
dialoguer = { default-features = false, version = "0.11" } dialoguer = { default-features = false, version = "0.11" }
digest = { default-features = false, version = "0.10" } digest = { default-features = false, version = "0.10" }
dirs-next = "2.0" dirs = "5.0"
dirs-sys = "0.4"
dtparse = "2.0" dtparse = "2.0"
encoding_rs = "0.8" encoding_rs = "0.8"
fancy-regex = "0.13" fancy-regex = "0.13"
@ -115,12 +116,12 @@ mockito = { version = "1.4", default-features = false }
native-tls = "0.2" native-tls = "0.2"
nix = { version = "0.28", default-features = false } nix = { version = "0.28", default-features = false }
notify-debouncer-full = { version = "0.3", default-features = false } notify-debouncer-full = { version = "0.3", default-features = false }
nu-ansi-term = "0.50.0" nu-ansi-term = "0.50.1"
num-format = "0.4" num-format = "0.4"
num-traits = "0.2" num-traits = "0.2"
omnipath = "0.1" omnipath = "0.1"
once_cell = "1.18" once_cell = "1.18"
open = "5.2" open = "5.3"
os_pipe = { version = "1.2", features = ["io_safety"] } os_pipe = { version = "1.2", features = ["io_safety"] }
pathdiff = "0.2" pathdiff = "0.2"
percent-encoding = "2" percent-encoding = "2"
@ -137,7 +138,7 @@ quote = "1.0"
rand = "0.8" rand = "0.8"
ratatui = "0.26" ratatui = "0.26"
rayon = "1.10" rayon = "1.10"
reedline = "0.32.0" reedline = "0.33.0"
regex = "1.9.5" regex = "1.9.5"
rmp = "0.8" rmp = "0.8"
rmp-serde = "1.3" rmp-serde = "1.3"
@ -145,7 +146,7 @@ ropey = "1.6.1"
roxmltree = "0.19" roxmltree = "0.19"
rstest = { version = "0.18", default-features = false } rstest = { version = "0.18", default-features = false }
rusqlite = "0.31" rusqlite = "0.31"
rust-embed = "8.4.0" rust-embed = "8.5.0"
same-file = "1.0" same-file = "1.0"
serde = { version = "1.0", default-features = false } serde = { version = "1.0", default-features = false }
serde_json = "1.0" serde_json = "1.0"
@ -173,35 +174,36 @@ uu_mv = "0.0.27"
uu_whoami = "0.0.27" uu_whoami = "0.0.27"
uu_uname = "0.0.27" uu_uname = "0.0.27"
uucore = "0.0.27" uucore = "0.0.27"
uuid = "1.9.1" uuid = "1.10.0"
v_htmlescape = "0.15.0" v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
which = "6.0.0" which = "6.0.0"
windows = "0.54" windows = "0.54"
windows-sys = "0.48"
winreg = "0.52" winreg = "0.52"
[dependencies] [dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.95.1" } nu-cli = { path = "./crates/nu-cli", version = "0.96.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" } nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" } nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.2" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true } nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.2", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" } nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.2" }
nu-command = { path = "./crates/nu-command", version = "0.95.1" } nu-command = { path = "./crates/nu-command", version = "0.96.2" }
nu-engine = { path = "./crates/nu-engine", version = "0.95.1" } nu-engine = { path = "./crates/nu-engine", version = "0.96.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.95.1" } nu-explore = { path = "./crates/nu-explore", version = "0.96.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" } nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.95.1" } nu-parser = { path = "./crates/nu-parser", version = "0.96.2" }
nu-path = { path = "./crates/nu-path", version = "0.95.1" } nu-path = { path = "./crates/nu-path", version = "0.96.2" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" } nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" } nu-protocol = { path = "./crates/nu-protocol", version = "0.96.2" }
nu-std = { path = "./crates/nu-std", version = "0.95.1" } nu-std = { path = "./crates/nu-std", version = "0.96.2" }
nu-system = { path = "./crates/nu-system", version = "0.95.1" } nu-system = { path = "./crates/nu-system", version = "0.96.2" }
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" } nu-utils = { path = "./crates/nu-utils", version = "0.96.2" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true } crossterm = { workspace = true }
ctrlc = { workspace = true } ctrlc = { workspace = true }
dirs-next = { workspace = true } dirs = { 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 }
@ -225,11 +227,11 @@ nix = { workspace = true, default-features = false, features = [
] } ] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" } nu-test-support = { path = "./crates/nu-test-support", version = "0.96.2" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.2" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.2" }
assert_cmd = "2.0" assert_cmd = "2.0"
dirs-next = { workspace = true } dirs = { workspace = true }
tango-bench = "0.5" tango-bench = "0.5"
pretty_assertions = { workspace = true } pretty_assertions = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
@ -303,8 +305,8 @@ bench = false
# To use a development version of a dependency please use a global override here # To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious # changing versions in each sub-crate of the workspace is tedious
[patch.crates-io] # [patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
# Run all benchmarks with `cargo bench` # Run all benchmarks with `cargo bench`

29
SECURITY.md Normal file
View File

@ -0,0 +1,29 @@
# Security Policy
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
## Supported Versions
As Nushell is still under very active pre-stable development, the only version the core team prioritizes for security and safety fixes is the [most recent version as published on GitHub](https://github.com/nushell/nushell/releases/latest).
Only if you provide a strong reasoning and the necessary resources, will we consider blessing a backported fix with an official patch release for a previous version.
## Reporting a Vulnerability
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
Please try to answer the following questions:
- How can we reach you for further questions?
- What is the bug? Which system of Nushell may be affected?
- Do you have proof-of-concept for a potential exploit or have you observed an exploit in the wild?
- What is your assessment of the severity based on what could be impacted should the bug be exploited?
- Are additional people aware of the issue or deserve credit for identifying the issue?
We will try to get back to you within a week with:
- acknowledging the receipt of the report
- an initial plan of how we want to address this including the primary points of contact for further communication
- our preliminary assessment of how severe we judge the issue
- a proposal for how we can coordinate responsible disclosure (e.g. how we ship the bugfix, if we need to coordinate with distribution maintainers, when you can release a blog post if you want to etc.)
For purely *safety* related issues where the impact is severe by direct user action instead of malicious input or third parties, feel free to open a regular issue. If we deem that there may be an additional *security* risk on a *safety* issue we may continue discussions in a restricted forum.

View File

@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
bench = false bench = false
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-command = { path = "../nu-command", version = "0.95.1" } nu-command = { path = "../nu-command", version = "0.96.2" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.96.2" }
rstest = { workspace = true, default-features = false } rstest = { workspace = true, default-features = false }
tempfile = { workspace = true } tempfile = { workspace = true }
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.95.1" } nu-path = { path = "../nu-path", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" } nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }

7
crates/nu-cli/README.md Normal file
View File

@ -0,0 +1,7 @@
This crate implements the core functionality of the interactive Nushell REPL and interfaces with `reedline`.
Currently implements the syntax highlighting and completions logic.
Furthermore includes a few commands that are specific to `reedline`
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -156,58 +156,34 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
//2. Create a record of either short or long columns and values //2. Create a record of either short or long columns and values
let item_id_value = Value::int( let item_id_value = Value::int(
match entry.id { entry
Some(id) => { .id
let ids = id.to_string(); .and_then(|id| id.to_string().parse::<i64>().ok())
match ids.parse::<i64>() { .unwrap_or_default(),
Ok(i) => i,
_ => 0i64,
}
}
None => 0i64,
},
head, head,
); );
let start_timestamp_value = Value::string( let start_timestamp_value = Value::string(
match entry.start_timestamp { entry
Some(time) => time.to_string(), .start_timestamp
None => "".into(), .map(|time| time.to_string())
}, .unwrap_or_default(),
head, head,
); );
let command_value = Value::string(entry.command_line, head); let command_value = Value::string(entry.command_line, head);
let session_id_value = Value::int( let session_id_value = Value::int(
match entry.session_id { entry
Some(sid) => { .session_id
let sids = sid.to_string(); .and_then(|id| id.to_string().parse::<i64>().ok())
match sids.parse::<i64>() { .unwrap_or_default(),
Ok(i) => i,
_ => 0i64,
}
}
None => 0i64,
},
head,
);
let hostname_value = Value::string(
match entry.hostname {
Some(host) => host,
None => "".into(),
},
head,
);
let cwd_value = Value::string(
match entry.cwd {
Some(cwd) => cwd,
None => "".into(),
},
head, head,
); );
let hostname_value = Value::string(entry.hostname.unwrap_or_default(), head);
let cwd_value = Value::string(entry.cwd.unwrap_or_default(), head);
let duration_value = Value::duration( let duration_value = Value::duration(
match entry.duration { entry
Some(d) => d.as_nanos().try_into().unwrap_or(0), .duration
None => 0, .and_then(|d| d.as_nanos().try_into().ok())
}, .unwrap_or(0),
head, head,
); );
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head); let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);

View File

@ -61,10 +61,12 @@ impl Command for KeybindingsList {
.map(|option| call.has_flag(engine_state, stack, option)) .map(|option| call.has_flag(engine_state, stack, option))
.collect::<Result<Vec<_>, ShellError>>()?; .collect::<Result<Vec<_>, ShellError>>()?;
let no_option_specified = presence.iter().all(|present| !*present);
let records = all_options let records = all_options
.iter() .iter()
.zip(presence) .zip(presence)
.filter(|(_, present)| *present) .filter(|(_, present)| no_option_specified || *present)
.flat_map(|(option, _)| get_records(option, call.head)) .flat_map(|(option, _)| get_records(option, call.head))
.collect(); .collect();

View File

@ -99,10 +99,9 @@ impl CommandCompletion {
suggestion: Suggestion { suggestion: Suggestion {
value: String::from_utf8_lossy(&x.0).to_string(), value: String::from_utf8_lossy(&x.0).to_string(),
description: x.1, description: x.1,
style: None,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
kind: Some(SuggestionKind::Command(x.2)), kind: Some(SuggestionKind::Command(x.2)),
}) })
@ -118,11 +117,9 @@ impl CommandCompletion {
.map(move |x| SemanticSuggestion { .map(move |x| SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: x, value: x,
description: None,
style: None,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
// TODO: is there a way to create a test? // TODO: is there a way to create a test?
kind: None, kind: None,
@ -136,11 +133,9 @@ impl CommandCompletion {
results.push(SemanticSuggestion { results.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: format!("^{}", external.suggestion.value), value: format!("^{}", external.suggestion.value),
description: None,
style: None,
extra: None,
span: external.suggestion.span, span: external.suggestion.span,
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
kind: external.kind, kind: external.kind,
}) })

View File

@ -443,14 +443,11 @@ pub fn map_value_completions<'a>(
return Some(SemanticSuggestion { return Some(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: s, value: s,
description: None,
style: None,
extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Type(x.get_type())), kind: Some(SuggestionKind::Type(x.get_type())),
}); });
@ -460,14 +457,11 @@ pub fn map_value_completions<'a>(
if let Ok(record) = x.as_record() { if let Ok(record) = x.as_record() {
let mut suggestion = Suggestion { let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string value: String::from(""), // Initialize with empty string
description: None,
style: None,
extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
append_whitespace: false, ..Suggestion::default()
}; };
// Iterate the cols looking for `value` and `description` // Iterate the cols looking for `value` and `description`

View File

@ -10,9 +10,7 @@ use nu_protocol::{
levenshtein_distance, Span, levenshtein_distance, Span,
}; };
use nu_utils::get_ls_colors; use nu_utils::get_ls_colors;
use std::path::{ use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
};
use super::SortBy; use super::SortBy;
@ -93,16 +91,16 @@ enum OriginalCwd {
} }
impl OriginalCwd { impl OriginalCwd {
fn apply(&self, mut p: PathBuiltFromString) -> String { fn apply(&self, mut p: PathBuiltFromString, path_separator: char) -> String {
match self { match self {
Self::None => {} Self::None => {}
Self::Home => p.parts.insert(0, "~".to_string()), Self::Home => p.parts.insert(0, "~".to_string()),
Self::Prefix(s) => p.parts.insert(0, s.clone()), Self::Prefix(s) => p.parts.insert(0, s.clone()),
}; };
let mut ret = p.parts.join(MAIN_SEPARATOR_STR); let mut ret = p.parts.join(&path_separator.to_string());
if p.isdir { if p.isdir {
ret.push(SEP); ret.push(path_separator);
} }
ret ret
} }
@ -133,6 +131,14 @@ pub fn complete_item(
) -> Vec<(nu_protocol::Span, String, Option<Style>)> { ) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
let partial = surround_remove(partial); let partial = surround_remove(partial);
let isdir = partial.ends_with(is_separator); let isdir = partial.ends_with(is_separator);
#[cfg(unix)]
let path_separator = SEP;
#[cfg(windows)]
let path_separator = partial
.chars()
.rfind(|c: &char| is_separator(*c))
.unwrap_or(SEP);
let cwd_pathbuf = Path::new(cwd).to_path_buf(); let cwd_pathbuf = Path::new(cwd).to_path_buf();
let ls_colors = (engine_state.config.use_ls_colors_completions let ls_colors = (engine_state.config.use_ls_colors_completions
&& engine_state.config.use_ansi_coloring) && engine_state.config.use_ansi_coloring)
@ -195,7 +201,7 @@ pub fn complete_item(
) )
.into_iter() .into_iter()
.map(|p| { .map(|p| {
let path = original_cwd.apply(p); let path = original_cwd.apply(p, path_separator);
let style = ls_colors.as_ref().map(|lsc| { let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata( lsc.style_for_path_with_metadata(
&path, &path,

View File

@ -48,14 +48,12 @@ impl Completer for DirectoryCompletion {
.map(move |x| SemanticSuggestion { .map(move |x| SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: x.1, value: x.1,
description: None,
style: x.2, style: x.2,
extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,
end: x.0.end - offset, end: x.0.end - offset,
}, },
append_whitespace: false, ..Suggestion::default()
}, },
// TODO???? // TODO????
kind: None, kind: None,

View File

@ -116,14 +116,13 @@ impl Completer for DotNuCompletion {
.map(move |x| SemanticSuggestion { .map(move |x| SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: x.1, value: x.1,
description: None,
style: x.2, style: x.2,
extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,
end: x.0.end - offset, end: x.0.end - offset,
}, },
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
// TODO???? // TODO????
kind: None, kind: None,

View File

@ -53,14 +53,12 @@ impl Completer for FileCompletion {
.map(move |x| SemanticSuggestion { .map(move |x| SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: x.1, value: x.1,
description: None,
style: x.2, style: x.2,
extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,
end: x.0.end - offset, end: x.0.end - offset,
}, },
append_whitespace: false, ..Suggestion::default()
}, },
// TODO???? // TODO????
kind: None, kind: None,

View File

@ -51,13 +51,12 @@ impl Completer for FlagCompletion {
suggestion: Suggestion { suggestion: Suggestion {
value: String::from_utf8_lossy(&named).to_string(), value: String::from_utf8_lossy(&named).to_string(),
description: Some(flag_desc.to_string()), description: Some(flag_desc.to_string()),
style: None,
extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
// TODO???? // TODO????
kind: None, kind: None,
@ -78,13 +77,12 @@ impl Completer for FlagCompletion {
suggestion: Suggestion { suggestion: Suggestion {
value: String::from_utf8_lossy(&named).to_string(), value: String::from_utf8_lossy(&named).to_string(),
description: Some(flag_desc.to_string()), description: Some(flag_desc.to_string()),
style: None,
extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
append_whitespace: true, append_whitespace: true,
..Suggestion::default()
}, },
// TODO???? // TODO????
kind: None, kind: None,

View File

@ -85,11 +85,8 @@ impl Completer for VariableCompletion {
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: env_var.0, value: env_var.0,
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Type(env_var.1.get_type())), kind: Some(SuggestionKind::Type(env_var.1.get_type())),
}); });
@ -157,11 +154,8 @@ impl Completer for VariableCompletion {
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: builtin.to_string(), value: builtin.to_string(),
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
// TODO is there a way to get the VarId to get the type??? // TODO is there a way to get the VarId to get the type???
kind: None, kind: None,
@ -184,11 +178,8 @@ impl Completer for VariableCompletion {
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Type( kind: Some(SuggestionKind::Type(
working_set.get_variable(*v.1).ty.clone(), working_set.get_variable(*v.1).ty.clone(),
@ -215,11 +206,8 @@ impl Completer for VariableCompletion {
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Type( kind: Some(SuggestionKind::Type(
working_set.get_variable(*v.1).ty.clone(), working_set.get_variable(*v.1).ty.clone(),
@ -255,11 +243,8 @@ fn nested_suggestions(
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: col.clone(), value: col.clone(),
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(kind.clone()), kind: Some(kind.clone()),
}); });
@ -272,11 +257,8 @@ fn nested_suggestions(
output.push(SemanticSuggestion { output.push(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: column_name, value: column_name,
description: None,
style: None,
extra: None,
span: current_span, span: current_span,
append_whitespace: false, ..Suggestion::default()
}, },
kind: Some(kind.clone()), kind: Some(kind.clone()),
}); });

View File

@ -76,12 +76,21 @@ pub fn evaluate_file(
trace!("parsing file: {}", file_path_str); trace!("parsing file: {}", file_path_str);
let block = parse(&mut working_set, Some(file_path_str), &file, false); let block = parse(&mut working_set, Some(file_path_str), &file, false);
if let Some(warning) = working_set.parse_warnings.first() {
report_error(&working_set, warning);
}
// If any parse errors were found, report the first error and exit. // If any parse errors were found, report the first error and exit.
if let Some(err) = working_set.parse_errors.first() { if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err); report_error(&working_set, err);
std::process::exit(1); std::process::exit(1);
} }
if let Some(err) = working_set.compile_errors.first() {
report_error(&working_set, err);
// Not a fatal error, for now
}
// Look for blocks whose name starts with "main" and replace it with the filename. // Look for blocks whose name starts with "main" and replace it with the filename.
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) { for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
if block.signature.name == "main" { if block.signature.name == "main" {

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod commands; mod commands;
mod completions; mod completions;
mod config_files; mod config_files;

View File

@ -110,13 +110,12 @@ impl NuHelpCompleter {
Suggestion { Suggestion {
value: decl.name().into(), value: decl.name().into(),
description: Some(long_desc), description: Some(long_desc),
style: None,
extra: Some(extra), extra: Some(extra),
span: reedline::Span { span: reedline::Span {
start: pos - line.len(), start: pos - line.len(),
end: pos, end: pos,
}, },
append_whitespace: false, ..Suggestion::default()
} }
}) })
.collect() .collect()

View File

@ -142,10 +142,9 @@ fn convert_to_suggestions(
vec![Suggestion { vec![Suggestion {
value: text, value: text,
description, description,
style: None,
extra, extra,
span, span,
append_whitespace: false, ..Suggestion::default()
}] }]
} }
Value::List { vals, .. } => vals Value::List { vals, .. } => vals
@ -154,9 +153,6 @@ fn convert_to_suggestions(
.collect(), .collect(),
_ => vec![Suggestion { _ => vec![Suggestion {
value: format!("Not a record: {value:?}"), value: format!("Not a record: {value:?}"),
description: None,
style: None,
extra: None,
span: reedline::Span { span: reedline::Span {
start: if only_buffer_difference { start: if only_buffer_difference {
pos - line.len() pos - line.len()
@ -169,7 +165,7 @@ fn convert_to_suggestions(
line.len() line.len()
}, },
}, },
append_whitespace: false, ..Suggestion::default()
}], }],
} }
} }

View File

@ -429,6 +429,14 @@ fn find_matching_block_end_in_expr(
) )
}), }),
Expr::Collect(_, expr) => find_matching_block_end_in_expr(
line,
working_set,
expr,
global_span_offset,
global_cursor_offset,
),
Expr::Block(block_id) Expr::Block(block_id)
| Expr::Closure(block_id) | Expr::Closure(block_id)
| Expr::RowCondition(block_id) | Expr::RowCondition(block_id)

View File

@ -321,16 +321,10 @@ mod test {
let env = engine_state.render_env_vars(); let env = engine_state.render_env_vars();
assert!( assert!(matches!(env.get("FOO"), Some(&Value::String { val, .. }) if val == "foo"));
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo") assert!(matches!(env.get("SYMBOLS"), Some(&Value::String { val, .. }) if val == symbols));
); assert!(matches!(env.get(symbols), Some(&Value::String { val, .. }) if val == "symbols"));
assert!( assert!(env.contains_key("PWD"));
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
);
assert!(
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
);
assert!(env.get(&"PWD".to_string()).is_some());
assert_eq!(env.len(), 4); assert_eq!(env.len(), 4);
} }
} }

View File

@ -0,0 +1,7 @@
use nu_test_support::nu;
#[test]
fn not_empty() {
let result = nu!("keybindings list | is-not-empty");
assert_eq!(result.out, "true");
}

View File

@ -1 +1,2 @@
mod keybindings_list;
mod nu_highlight; mod nu_highlight;

View File

@ -32,7 +32,6 @@ fn completer() -> NuCompleter {
fn completer_strings() -> NuCompleter { fn completer_strings() -> NuCompleter {
// Create a new engine // Create a new engine
let (dir, _, mut engine, mut stack) = new_engine(); let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example // Add record value as example
let record = r#"def animals [] { ["cat", "dog", "eel" ] } let record = r#"def animals [] { ["cat", "dog", "eel" ] }
def my-command [animal: string@animals] { print $animal }"#; def my-command [animal: string@animals] { print $animal }"#;
@ -123,28 +122,28 @@ fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter
let suggestions = completer.complete("tst --", 6); let suggestions = completer.complete("tst --", 6);
let expected: Vec<String> = vec!["--help".into(), "--mod".into()]; let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
// dbg!(&expected, &suggestions); // dbg!(&expected, &suggestions);
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) { fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -", 5); let suggestions = completer.complete("tst -", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) { fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-c ", 4); let suggestions = completer_strings.complete("my-c ", 4);
let expected: Vec<String> = vec!["my-command".into()]; let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) { fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 11); let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
@ -153,7 +152,7 @@ fn variables_customcompletion_subcommands_with_customcompletion_2(
) { ) {
let suggestions = completer_strings.complete("my-command ", 11); let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[test] #[test]
@ -182,19 +181,19 @@ fn dotnu_completions() {
let completion_str = "source-env ".to_string(); let completion_str = "source-env ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
match_suggestions(expected.clone(), suggestions); match_suggestions(&expected, &suggestions);
// Test use completion // Test use completion
let completion_str = "use ".to_string(); let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
match_suggestions(expected.clone(), suggestions); match_suggestions(&expected, &suggestions);
// Test overlay use completion // Test overlay use completion
let completion_str = "overlay use ".to_string(); let completion_str = "overlay use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[test] #[test]
@ -258,8 +257,22 @@ fn file_completions() {
folder(dir.join(".hidden_folder")), folder(dir.join(".hidden_folder")),
]; ];
#[cfg(windows)]
{
let separator = '/';
let target_dir = format!("cp {dir_str}{separator}");
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
let expected_slash_paths: Vec<String> = expected_paths
.iter()
.map(|s| s.replace('\\', "/"))
.collect();
match_suggestions(&expected_slash_paths, &slash_suggestions);
}
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completions for a file // Test completions for a file
let target_dir = format!("cp {}", folder(dir.join("another"))); let target_dir = format!("cp {}", folder(dir.join("another")));
@ -269,17 +282,91 @@ fn file_completions() {
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))]; let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completions for hidden files // Test completions for hidden files
let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder"))); let target_dir = format!("ls {}{MAIN_SEPARATOR}.", folder(dir.join(".hidden_folder")));
let suggestions = completer.complete(&target_dir, target_dir.len()); let suggestions = completer.complete(&target_dir, target_dir.len());
let expected_paths: Vec<String> = let expected_paths: Vec<String> =
vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))]; vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))];
#[cfg(windows)]
{
let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder")));
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
let expected_slash: Vec<String> = expected_paths
.iter()
.map(|s| s.replace('\\', "/"))
.collect();
match_suggestions(&expected_slash, &slash_suggestions);
}
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
}
#[cfg(windows)]
#[test]
fn file_completions_with_mixed_separators() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_dotnu_engine();
// Instantiate a new completer
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Create Expected values
let expected_paths: Vec<String> = vec![
file(dir.join("lib-dir1").join("bar.nu")),
file(dir.join("lib-dir1").join("baz.nu")),
file(dir.join("lib-dir1").join("xyzzy.nu")),
];
let expecetd_slash_paths: Vec<String> = expected_paths
.iter()
.map(|s| s.replace(MAIN_SEPARATOR, "/"))
.collect();
let target_dir = format!("ls {dir_str}/lib-dir1/");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expecetd_slash_paths, &suggestions);
let target_dir = format!("cp {dir_str}\\lib-dir1/");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expecetd_slash_paths, &suggestions);
let target_dir = format!("ls {dir_str}/lib-dir1\\/");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expecetd_slash_paths, &suggestions);
let target_dir = format!("ls {dir_str}\\lib-dir1\\/");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expecetd_slash_paths, &suggestions);
let target_dir = format!("ls {dir_str}\\lib-dir1\\");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expected_paths, &suggestions);
let target_dir = format!("ls {dir_str}/lib-dir1\\");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expected_paths, &suggestions);
let target_dir = format!("ls {dir_str}/lib-dir1/\\");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expected_paths, &suggestions);
let target_dir = format!("ls {dir_str}\\lib-dir1/\\");
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(&expected_paths, &suggestions);
} }
#[test] #[test]
@ -303,7 +390,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completions for the files whose name begin with "h" // Test completions for the files whose name begin with "h"
// and are present under directories whose names begin with "pa" // and are present under directories whose names begin with "pa"
@ -324,7 +411,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completion for all files under directories whose names begin with "pa" // Test completion for all files under directories whose names begin with "pa"
let dir_str = folder(dir.join("pa")); let dir_str = folder(dir.join("pa"));
@ -345,7 +432,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completion for a single file // Test completion for a single file
let dir_str = file(dir.join("fi").join("so")); let dir_str = file(dir.join("fi").join("so"));
@ -356,7 +443,7 @@ fn partial_completions() {
let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))]; let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completion where there is a sneaky `..` in the path // Test completion where there is a sneaky `..` in the path
let dir_str = file(dir.join("par").join("..").join("fi").join("so")); let dir_str = file(dir.join("par").join("..").join("fi").join("so"));
@ -392,7 +479,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completion for all files under directories whose names begin with "pa" // Test completion for all files under directories whose names begin with "pa"
let file_str = file(dir.join("partial-a").join("have")); let file_str = file(dir.join("partial-a").join("have"));
@ -406,7 +493,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
// Test completion for all files under directories whose names begin with "pa" // Test completion for all files under directories whose names begin with "pa"
let file_str = file(dir.join("partial-a").join("have_ext.")); let file_str = file(dir.join("partial-a").join("have_ext."));
@ -420,7 +507,7 @@ fn partial_completions() {
]; ];
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
} }
#[test] #[test]
@ -455,15 +542,16 @@ fn command_ls_with_filecompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
let target_dir = "ls custom_completion."; let target_dir = "ls custom_completion.";
let suggestions = completer.complete(target_dir, target_dir.len()); let suggestions = completer.complete(target_dir, target_dir.len());
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()]; let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions);
} }
#[test] #[test]
fn command_open_with_filecompletion() { fn command_open_with_filecompletion() {
let (_, _, engine, stack) = new_engine(); let (_, _, engine, stack) = new_engine();
@ -496,14 +584,14 @@ fn command_open_with_filecompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
let target_dir = "open custom_completion."; let target_dir = "open custom_completion.";
let suggestions = completer.complete(target_dir, target_dir.len()); let suggestions = completer.complete(target_dir, target_dir.len());
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()]; let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions);
} }
#[test] #[test]
@ -538,7 +626,7 @@ fn command_rm_with_globcompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -573,7 +661,7 @@ fn command_cp_with_globcompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -608,7 +696,7 @@ fn command_save_with_filecompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -643,7 +731,7 @@ fn command_touch_with_filecompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -678,7 +766,7 @@ fn command_watch_with_filecompletion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[rstest] #[rstest]
@ -686,19 +774,19 @@ fn subcommand_completions(mut subcommand_completer: NuCompleter) {
let prefix = "foo br"; let prefix = "foo br";
let suggestions = subcommand_completer.complete(prefix, prefix.len()); let suggestions = subcommand_completer.complete(prefix, prefix.len());
match_suggestions( match_suggestions(
vec!["foo bar".to_string(), "foo aabrr".to_string()], &vec!["foo bar".to_string(), "foo aabrr".to_string()],
suggestions, &suggestions,
); );
let prefix = "foo b"; let prefix = "foo b";
let suggestions = subcommand_completer.complete(prefix, prefix.len()); let suggestions = subcommand_completer.complete(prefix, prefix.len());
match_suggestions( match_suggestions(
vec![ &vec![
"foo bar".to_string(), "foo bar".to_string(),
"foo abaz".to_string(), "foo abaz".to_string(),
"foo aabrr".to_string(), "foo aabrr".to_string(),
], ],
suggestions, &suggestions,
); );
} }
@ -724,7 +812,7 @@ fn file_completion_quoted() {
format!("`{}`", folder("test dir")), format!("`{}`", folder("test dir")),
]; ];
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
let dir: PathBuf = "test dir".into(); let dir: PathBuf = "test dir".into();
let target_dir = format!("open '{}'", folder(dir.clone())); let target_dir = format!("open '{}'", folder(dir.clone()));
@ -735,7 +823,7 @@ fn file_completion_quoted() {
format!("`{}`", file(dir.join("single quote"))), format!("`{}`", file(dir.join("single quote"))),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -770,7 +858,7 @@ fn flag_completions() {
]; ];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[test] #[test]
@ -794,8 +882,21 @@ fn folder_with_directorycompletions() {
folder(dir.join(".hidden_folder")), folder(dir.join(".hidden_folder")),
]; ];
#[cfg(windows)]
{
let target_dir = format!("cd {dir_str}/");
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
let expected_slash_paths: Vec<String> = expected_paths
.iter()
.map(|s| s.replace('\\', "/"))
.collect();
match_suggestions(&expected_slash_paths, &slash_suggestions);
}
// Match the results // Match the results
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
} }
#[test] #[test]
@ -833,11 +934,11 @@ fn variables_completions() {
"plugin-path".into(), "plugin-path".into(),
"startup-time".into(), "startup-time".into(),
"temp-path".into(), "temp-path".into(),
"vendor-autoload-dir".into(), "vendor-autoload-dirs".into(),
]; ];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for $nu.h (filter) // Test completions for $nu.h (filter)
let suggestions = completer.complete("$nu.h", 5); let suggestions = completer.complete("$nu.h", 5);
@ -851,7 +952,7 @@ fn variables_completions() {
]; ];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for $nu.os-info // Test completions for $nu.os-info
let suggestions = completer.complete("$nu.os-info.", 12); let suggestions = completer.complete("$nu.os-info.", 12);
@ -863,7 +964,7 @@ fn variables_completions() {
"name".into(), "name".into(),
]; ];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for custom var // Test completions for custom var
let suggestions = completer.complete("$actor.", 7); let suggestions = completer.complete("$actor.", 7);
@ -873,7 +974,7 @@ fn variables_completions() {
let expected: Vec<String> = vec!["age".into(), "name".into()]; let expected: Vec<String> = vec!["age".into(), "name".into()];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for custom var (filtering) // Test completions for custom var (filtering)
let suggestions = completer.complete("$actor.n", 8); let suggestions = completer.complete("$actor.n", 8);
@ -883,7 +984,7 @@ fn variables_completions() {
let expected: Vec<String> = vec!["name".into()]; let expected: Vec<String> = vec!["name".into()];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for $env // Test completions for $env
let suggestions = completer.complete("$env.", 5); let suggestions = completer.complete("$env.", 5);
@ -896,7 +997,7 @@ fn variables_completions() {
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()]; let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
// Test completions for $env // Test completions for $env
let suggestions = completer.complete("$env.T", 6); let suggestions = completer.complete("$env.T", 6);
@ -906,12 +1007,12 @@ fn variables_completions() {
let expected: Vec<String> = vec!["TEST".into()]; let expected: Vec<String> = vec!["TEST".into()];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
let suggestions = completer.complete("$", 1); let suggestions = completer.complete("$", 1);
let expected: Vec<String> = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()]; let expected: Vec<String> = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[test] #[test]
@ -930,7 +1031,7 @@ fn alias_of_command_and_flags() {
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()]; let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -949,7 +1050,7 @@ fn alias_of_basic_command() {
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()]; let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[test] #[test]
@ -971,7 +1072,7 @@ fn alias_of_another_alias() {
#[cfg(not(windows))] #[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()]; let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> { fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
@ -1034,35 +1135,35 @@ fn unknown_command_completion() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[rstest] #[rstest]
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) { fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h", 5); let suggestions = completer.complete("tst -h", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) { fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c", 11); let suggestions = completer_strings.complete("my-command c", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) { fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command c | ls", 11); let suggestions = completer_strings.complete("my-command c | ls", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) { fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -h | ls", 5); let suggestions = completer.complete("tst -h | ls", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[test] #[test]
@ -1096,77 +1197,77 @@ fn filecompletions_triggers_after_cursor() {
".hidden_folder/".to_string(), ".hidden_folder/".to_string(),
]; ];
match_suggestions(expected_paths, suggestions); match_suggestions(&expected_paths, &suggestions);
} }
#[rstest] #[rstest]
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) { fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam ", 5); let suggestions = extern_completer.complete("spam ", 5);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) { fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --foo=", 11); let suggestions = extern_completer.complete("spam --foo=", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) { fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --foo ", 11); let suggestions = extern_completer.complete("spam --foo ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) { fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -f ", 8); let suggestions = extern_completer.complete("spam -f ", 8);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) { fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -b ", 8); let suggestions = extern_completer.complete("spam -b ", 8);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()]; let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn extern_complete_flags(mut extern_completer: NuCompleter) { fn extern_complete_flags(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -", 6); let suggestions = extern_completer.complete("spam -", 6);
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()]; let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) { fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) {
let suggestions = custom_completer.complete("cmd foo bar", 8); let suggestions = custom_completer.complete("cmd foo bar", 8);
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()]; let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) { fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) {
let suggestions = custom_completer.complete("cmd foo bar", 8); let suggestions = custom_completer.complete("cmd foo bar", 8);
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()]; let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) { fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) {
let suggestions = custom_completer.complete("cmd foo bar", 11); let suggestions = custom_completer.complete("cmd foo bar", 11);
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into()]; let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[rstest] #[rstest]
fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) { fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) {
let suggestions = custom_completer.complete("cmd foo bar ", 12); let suggestions = custom_completer.complete("cmd foo bar ", 12);
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into(), "".into()]; let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into(), "".into()];
match_suggestions(expected, suggestions); match_suggestions(&expected, &suggestions);
} }
#[ignore = "was reverted, still needs fixing"] #[ignore = "was reverted, still needs fixing"]

View File

@ -186,7 +186,7 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
} }
// match a list of suggestions with the expected values // match a list of suggestions with the expected values
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) { pub fn match_suggestions(expected: &Vec<String>, suggestions: &Vec<Suggestion>) {
let expected_len = expected.len(); let expected_len = expected.len();
let suggestions_len = suggestions.len(); let suggestions_len = suggestions.len();
if expected_len != suggestions_len { if expected_len != suggestions_len {
@ -196,13 +196,13 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
Expected: {expected:#?}\n" Expected: {expected:#?}\n"
) )
} }
assert_eq!(
expected, let suggestoins_str = suggestions
suggestions .iter()
.into_iter() .map(|it| it.value.clone())
.map(|it| it.value) .collect::<Vec<_>>();
.collect::<Vec<_>>()
); assert_eq!(expected, &suggestoins_str);
} }
// append the separator to the converted path // append the separator to the converted path

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-base" name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.95.1" version = "0.96.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.95.1" } nu-path = { path = "../nu-path", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
indexmap = { workspace = true } indexmap = { workspace = true }
miette = { workspace = true } miette = { workspace = true }

View File

@ -0,0 +1,5 @@
Utilities used by the different `nu-command`/`nu-cmd-*` crates, should not contain any full `Command` implementations.
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
pub mod formats; pub mod formats;
pub mod hook; pub mod hook;
pub mod input_handler; pub mod input_handler;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-extra" name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.95.1" version = "0.96.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,13 +13,13 @@ version = "0.95.1"
bench = false bench = false
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-json = { version = "0.95.1", path = "../nu-json" } nu-json = { version = "0.96.2", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" } nu-pretty-hex = { version = "0.96.2", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
# Potential dependencies for extras # Potential dependencies for extras
heck = { workspace = true } heck = { workspace = true }
@ -33,6 +33,6 @@ v_htmlescape = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-command = { path = "../nu-command", version = "0.95.1" } nu-command = { path = "../nu-command", version = "0.96.2" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.96.2" }

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod example_test; mod example_test;
pub mod extra; pub mod extra;
pub use extra::*; pub use extra::*;

View File

@ -6,22 +6,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
itertools = { workspace = true } itertools = { workspace = true }
shadow-rs = { version = "0.29", default-features = false } shadow-rs = { version = "0.30", default-features = false }
[build-dependencies] [build-dependencies]
shadow-rs = { version = "0.29", default-features = false } shadow-rs = { version = "0.30", default-features = false }
[features] [features]
mimalloc = [] mimalloc = []

View File

@ -21,7 +21,9 @@ impl Command for Break {
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check: r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"# https://www.nushell.sh/book/thinking_in_nu.html
break can only be used in while, loop, and for loops. It can not be used with each or other filter commands"#
} }
fn command_type(&self) -> CommandType { fn command_type(&self) -> CommandType {

View File

@ -21,7 +21,9 @@ impl Command for Continue {
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check: r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"# https://www.nushell.sh/book/thinking_in_nu.html
continue can only be used in while, loop, and for loops. It can not be used with each or other filter commands"#
} }
fn command_type(&self) -> CommandType { fn command_type(&self) -> CommandType {

View File

@ -4,7 +4,7 @@ use nu_protocol::{
ast::Block, ast::Block,
debugger::WithoutDebug, debugger::WithoutDebug,
engine::{StateDelta, StateWorkingSet}, engine::{StateDelta, StateWorkingSet},
Range, report_error_new, Range,
}; };
use std::{ use std::{
sync::Arc, sync::Arc,
@ -124,7 +124,10 @@ pub fn eval_block(
nu_engine::eval_block::<WithoutDebug>(engine_state, &mut stack, &block, input) nu_engine::eval_block::<WithoutDebug>(engine_state, &mut stack, &block, input)
.and_then(|data| data.into_value(Span::test_data())) .and_then(|data| data.into_value(Span::test_data()))
.unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", "TODO", err)) .unwrap_or_else(|err| {
report_error_new(engine_state, &err);
panic!("test eval error in `{}`: {:?}", "TODO", err)
})
} }
pub fn check_example_evaluates_to_expected_output( pub fn check_example_evaluates_to_expected_output(

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod core_commands; mod core_commands;
mod default_context; mod default_context;
pub mod example_support; pub mod example_support;

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-plugin" name = "nu-cmd-plugin"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
version = "0.95.1" version = "0.96.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.95.1" } nu-path = { path = "../nu-path", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2" }
itertools = { workspace = true } itertools = { workspace = true }

View File

@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-color-config" name = "nu-color-config"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.95.1" } nu-json = { path = "../nu-json", version = "0.96.2" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.96.2" }

View File

@ -0,0 +1,5 @@
Logic to resolve colors for syntax highlighting and output formatting
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod color_config; mod color_config;
mod matching_brackets_style; mod matching_brackets_style;
mod nu_style; mod nu_style;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.95.1" version = "0.96.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,21 +13,21 @@ version = "0.95.1"
bench = false bench = false
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" } nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-glob = { path = "../nu-glob", version = "0.95.1" } nu-glob = { path = "../nu-glob", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.95.1" } nu-json = { path = "../nu-json", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.95.1" } nu-path = { path = "../nu-path", version = "0.96.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-system = { path = "../nu-system", version = "0.95.1" } nu-system = { path = "../nu-system", version = "0.96.2" }
nu-table = { path = "../nu-table", version = "0.95.1" } nu-table = { path = "../nu-table", version = "0.96.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.95.1" } nuon = { path = "../nuon", version = "0.96.2" }
alphanumeric-sort = { workspace = true } alphanumeric-sort = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
@ -137,10 +137,10 @@ sqlite = ["rusqlite"]
trash-support = ["trash"] trash-support = ["trash"]
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.96.2" }
dirs-next = { workspace = true } dirs = { workspace = true }
mockito = { workspace = true, default-features = false } mockito = { workspace = true, default-features = false }
quickcheck = { workspace = true } quickcheck = { workspace = true }
quickcheck_macros = { workspace = true } quickcheck_macros = { workspace = true }

View File

@ -0,0 +1,7 @@
This crate contains the majority of our commands
We allow ourselves to move some of the commands in `nu-command` to `nu-cmd-*` crates as needed.
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -150,13 +150,9 @@ fn fill(
FillAlignment::Left FillAlignment::Left
}; };
let width = if let Some(arg) = width_arg { arg } else { 1 }; let width = width_arg.unwrap_or(1);
let character = if let Some(arg) = character_arg { let character = character_arg.unwrap_or_else(|| " ".to_string());
arg
} else {
" ".to_string()
};
let arg = Arguments { let arg = Arguments {
width, width,

View File

@ -424,11 +424,7 @@ pub fn value_to_sql(value: Value) -> Result<Box<dyn rusqlite::ToSql>, ShellError
Value::Filesize { val, .. } => Box::new(val), Value::Filesize { val, .. } => Box::new(val),
Value::Duration { val, .. } => Box::new(val), Value::Duration { val, .. } => Box::new(val),
Value::Date { val, .. } => Box::new(val), Value::Date { val, .. } => Box::new(val),
Value::String { val, .. } => { Value::String { val, .. } => Box::new(val),
// don't store ansi escape sequences in the database
// escape single quotes
Box::new(nu_utils::strip_ansi_unlikely(&val).into_owned())
}
Value::Binary { val, .. } => Box::new(val), Value::Binary { val, .. } => Box::new(val),
Value::Nothing { .. } => Box::new(rusqlite::types::Null), Value::Nothing { .. } => Box::new(rusqlite::types::Null),
val => { val => {

View File

@ -42,32 +42,32 @@ impl Command for MetadataSet {
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, mut input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?; let ds_fp: Option<String> = call.get_flag(engine_state, stack, "datasource-filepath")?;
let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?; let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?;
let content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?; let content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?;
let signals = engine_state.signals().clone();
let metadata = input let mut metadata = match &mut input {
.metadata() PipelineData::Value(_, metadata)
.clone() | PipelineData::ListStream(_, metadata)
.unwrap_or_default() | PipelineData::ByteStream(_, metadata) => metadata.take().unwrap_or_default(),
.with_content_type(content_type); PipelineData::Empty => return Err(ShellError::PipelineEmpty { dst_span: head }),
};
if let Some(content_type) = content_type {
metadata.content_type = Some(content_type);
}
match (ds_fp, ds_ls) { match (ds_fp, ds_ls) {
(Some(path), false) => Ok(input.into_pipeline_data_with_metadata( (Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()),
head, (None, true) => metadata.data_source = DataSource::Ls,
signals, (Some(_), true) => (), // TODO: error here
metadata.with_data_source(DataSource::FilePath(path.into())), (None, false) => (),
)),
(None, true) => Ok(input.into_pipeline_data_with_metadata(
head,
signals,
metadata.with_data_source(DataSource::Ls),
)),
_ => Ok(input.into_pipeline_data_with_metadata(head, signals, metadata)),
} }
Ok(input.set_metadata(Some(metadata)))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -85,7 +85,9 @@ impl Command for MetadataSet {
Example { Example {
description: "Set the metadata of a file path", description: "Set the metadata of a file path",
example: "'crates' | metadata set --content-type text/plain | metadata", example: "'crates' | metadata set --content-type text/plain | metadata",
result: Some(Value::record(record!("content_type" => Value::string("text/plain", Span::test_data())), Span::test_data())), result: Some(Value::test_record(record! {
"content_type" => Value::test_string("text/plain"),
})),
}, },
] ]
} }

View File

@ -1,5 +1,8 @@
use nu_engine::{command_prelude::*, ClosureEvalOnce}; use nu_engine::{command_prelude::*, ClosureEvalOnce};
use nu_protocol::{debugger::Profiler, engine::Closure}; use nu_protocol::{
debugger::{Profiler, ProfilerOptions},
engine::Closure,
};
#[derive(Clone)] #[derive(Clone)]
pub struct DebugProfile; pub struct DebugProfile;
@ -28,6 +31,7 @@ impl Command for DebugProfile {
Some('v'), Some('v'),
) )
.switch("expr", "Collect expression types", Some('x')) .switch("expr", "Collect expression types", Some('x'))
.switch("instructions", "Collect IR instructions", Some('i'))
.switch("lines", "Collect line numbers", Some('l')) .switch("lines", "Collect line numbers", Some('l'))
.named( .named(
"max-depth", "max-depth",
@ -91,19 +95,23 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati
let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?; let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?;
let collect_values = call.has_flag(engine_state, stack, "values")?; let collect_values = call.has_flag(engine_state, stack, "values")?;
let collect_exprs = call.has_flag(engine_state, stack, "expr")?; let collect_exprs = call.has_flag(engine_state, stack, "expr")?;
let collect_instructions = call.has_flag(engine_state, stack, "instructions")?;
let collect_lines = call.has_flag(engine_state, stack, "lines")?; let collect_lines = call.has_flag(engine_state, stack, "lines")?;
let max_depth = call let max_depth = call
.get_flag(engine_state, stack, "max-depth")? .get_flag(engine_state, stack, "max-depth")?
.unwrap_or(2); .unwrap_or(2);
let profiler = Profiler::new( let profiler = Profiler::new(
max_depth, ProfilerOptions {
collect_spans, max_depth,
true, collect_spans,
collect_expanded_source, collect_source: true,
collect_values, collect_expanded_source,
collect_exprs, collect_values,
collect_lines, collect_exprs,
collect_instructions,
collect_lines,
},
call.span(), call.span(),
); );

View File

@ -31,6 +31,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
All, All,
Any, Any,
Append, Append,
Chunks,
Columns, Columns,
Compact, Compact,
Default, Default,
@ -290,7 +291,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
ToText, ToText,
ToToml, ToToml,
ToTsv, ToTsv,
Touch,
Upsert, Upsert,
Where, Where,
ToXml, ToXml,

View File

@ -31,7 +31,7 @@ mod test_examples {
check_example_evaluates_to_expected_output, check_example_evaluates_to_expected_output,
check_example_input_and_output_types_match_command_signature, check_example_input_and_output_types_match_command_signature,
}; };
use nu_cmd_lang::{Break, Echo, If, Let, Mut}; use nu_cmd_lang::{Break, Describe, Echo, If, Let, Mut};
use nu_protocol::{ use nu_protocol::{
engine::{Command, EngineState, StateWorkingSet}, engine::{Command, EngineState, StateWorkingSet},
Type, Type,
@ -81,6 +81,7 @@ mod test_examples {
working_set.add_decl(Box::new(Break)); working_set.add_decl(Box::new(Break));
working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Date));
working_set.add_decl(Box::new(Default)); working_set.add_decl(Box::new(Default));
working_set.add_decl(Box::new(Describe));
working_set.add_decl(Box::new(Each)); working_set.add_decl(Box::new(Each));
working_set.add_decl(Box::new(Echo)); working_set.add_decl(Box::new(Echo));
working_set.add_decl(Box::new(Enumerate)); working_set.add_decl(Box::new(Enumerate));

View File

@ -121,9 +121,11 @@ impl Command for Save {
} else { } else {
match stderr { match stderr {
ChildPipe::Pipe(mut pipe) => { ChildPipe::Pipe(mut pipe) => {
io::copy(&mut pipe, &mut io::sink()) io::copy(&mut pipe, &mut io::stderr())
}
ChildPipe::Tee(mut tee) => {
io::copy(&mut tee, &mut io::stderr())
} }
ChildPipe::Tee(mut tee) => io::copy(&mut tee, &mut io::sink()),
} }
.err_span(span)?; .err_span(span)?;
} }

View File

@ -30,6 +30,11 @@ impl Command for UMv {
example: "mv test.txt my/subdirectory", example: "mv test.txt my/subdirectory",
result: None, result: None,
}, },
Example {
description: "Move only if source file is newer than target file",
example: "mv -u new/test.txt old/",
result: None,
},
Example { Example {
description: "Move many files into a directory", description: "Move many files into a directory",
example: "mv *.txt my/subdirectory", example: "mv *.txt my/subdirectory",
@ -49,6 +54,11 @@ impl Command for UMv {
.switch("verbose", "explain what is being done.", Some('v')) .switch("verbose", "explain what is being done.", Some('v'))
.switch("progress", "display a progress bar", Some('p')) .switch("progress", "display a progress bar", Some('p'))
.switch("interactive", "prompt before overwriting", Some('i')) .switch("interactive", "prompt before overwriting", Some('i'))
.switch(
"update",
"move and overwrite only when the SOURCE file is newer than the destination file or when the destination file is missing",
Some('u')
)
.switch("no-clobber", "do not overwrite an existing file", Some('n')) .switch("no-clobber", "do not overwrite an existing file", Some('n'))
.rest( .rest(
"paths", "paths",
@ -77,6 +87,11 @@ impl Command for UMv {
} else { } else {
uu_mv::OverwriteMode::Force uu_mv::OverwriteMode::Force
}; };
let update = if call.has_flag(engine_state, stack, "update")? {
UpdateMode::ReplaceIfOlder
} else {
UpdateMode::ReplaceAll
};
#[allow(deprecated)] #[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?; let cwd = current_dir(engine_state, stack)?;
@ -164,7 +179,7 @@ impl Command for UMv {
verbose, verbose,
suffix: String::from("~"), suffix: String::from("~"),
backup: BackupMode::NoBackup, backup: BackupMode::NoBackup,
update: UpdateMode::ReplaceAll, update,
target_dir: None, target_dir: None,
no_target_dir: false, no_target_dir: false,
strip_slashes: false, strip_slashes: false,

View File

@ -61,6 +61,7 @@ impl Command for Watch {
"Watch all directories under `<path>` recursively. Will be ignored if `<path>` is a file (default: true)", "Watch all directories under `<path>` recursively. Will be ignored if `<path>` is a file (default: true)",
Some('r'), Some('r'),
) )
.switch("quiet", "Hide the initial status message (default: false)", Some('q'))
.switch("verbose", "Operate in verbose mode (default: false)", Some('v')) .switch("verbose", "Operate in verbose mode (default: false)", Some('v'))
.category(Category::FileSystem) .category(Category::FileSystem)
} }
@ -94,6 +95,8 @@ impl Command for Watch {
let verbose = call.has_flag(engine_state, stack, "verbose")?; let verbose = call.has_flag(engine_state, stack, "verbose")?;
let quiet = call.has_flag(engine_state, stack, "quiet")?;
let debounce_duration_flag: Option<Spanned<i64>> = let debounce_duration_flag: Option<Spanned<i64>> =
call.get_flag(engine_state, stack, "debounce-ms")?; call.get_flag(engine_state, stack, "debounce-ms")?;
let debounce_duration = match debounce_duration_flag { let debounce_duration = match debounce_duration_flag {
@ -161,7 +164,9 @@ impl Command for Watch {
// need to cache to make sure that rename event works. // need to cache to make sure that rename event works.
debouncer.cache().add_root(&path, recursive_mode); debouncer.cache().add_root(&path, recursive_mode);
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort."); if !quiet {
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
}
let mut closure = ClosureEval::new(engine_state, stack, closure); let mut closure = ClosureEval::new(engine_state, stack, closure);

View File

@ -0,0 +1,161 @@
use nu_engine::command_prelude::*;
use nu_protocol::ListStream;
use std::num::NonZeroUsize;
#[derive(Clone)]
pub struct Chunks;
impl Command for Chunks {
fn name(&self) -> &str {
"chunks"
}
fn signature(&self) -> Signature {
Signature::build("chunks")
.input_output_types(vec![
(Type::table(), Type::list(Type::table())),
(Type::list(Type::Any), Type::list(Type::list(Type::Any))),
])
.required("chunk_size", SyntaxShape::Int, "The size of each chunk.")
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Divide a list or table into chunks of `chunk_size`."
}
fn extra_usage(&self) -> &str {
"This command will error if `chunk_size` is negative or zero."
}
fn search_terms(&self) -> Vec<&str> {
vec!["batch", "group"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "[1 2 3 4] | chunks 2",
description: "Chunk a list into pairs",
result: Some(Value::test_list(vec![
Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
Value::test_list(vec![Value::test_int(3), Value::test_int(4)]),
])),
},
Example {
example: "[[foo bar]; [0 1] [2 3] [4 5] [6 7] [8 9]] | chunks 3",
description: "Chunk the rows of a table into triplets",
result: Some(Value::test_list(vec![
Value::test_list(vec![
Value::test_record(record! {
"foo" => Value::test_int(0),
"bar" => Value::test_int(1),
}),
Value::test_record(record! {
"foo" => Value::test_int(2),
"bar" => Value::test_int(3),
}),
Value::test_record(record! {
"foo" => Value::test_int(4),
"bar" => Value::test_int(5),
}),
]),
Value::test_list(vec![
Value::test_record(record! {
"foo" => Value::test_int(6),
"bar" => Value::test_int(7),
}),
Value::test_record(record! {
"foo" => Value::test_int(8),
"bar" => Value::test_int(9),
}),
]),
])),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let chunk_size: Value = call.req(engine_state, stack, 0)?;
let size =
usize::try_from(chunk_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue {
span: chunk_size.span(),
})?;
let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
msg: "`chunk_size` cannot be zero".into(),
val_span: chunk_size.span(),
call_span: head,
})?;
chunks(engine_state, input, size, head)
}
}
pub fn chunks(
engine_state: &EngineState,
input: PipelineData,
chunk_size: NonZeroUsize,
span: Span,
) -> Result<PipelineData, ShellError> {
match input {
PipelineData::Value(Value::List { vals, .. }, metadata) => {
let chunks = ChunksIter::new(vals, chunk_size, span);
let stream = ListStream::new(chunks, span, engine_state.signals().clone());
Ok(PipelineData::ListStream(stream, metadata))
}
PipelineData::ListStream(stream, metadata) => {
let stream = stream.modify(|iter| ChunksIter::new(iter, chunk_size, span));
Ok(PipelineData::ListStream(stream, metadata))
}
input => Err(input.unsupported_input_error("list", span)),
}
}
struct ChunksIter<I: Iterator<Item = Value>> {
iter: I,
size: usize,
span: Span,
}
impl<I: Iterator<Item = Value>> ChunksIter<I> {
fn new(iter: impl IntoIterator<IntoIter = I>, size: NonZeroUsize, span: Span) -> Self {
Self {
iter: iter.into_iter(),
size: size.into(),
span,
}
}
}
impl<I: Iterator<Item = Value>> Iterator for ChunksIter<I> {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
let first = self.iter.next()?;
let mut chunk = Vec::with_capacity(self.size); // delay allocation to optimize for empty iter
chunk.push(first);
chunk.extend((&mut self.iter).take(self.size - 1));
Some(Value::list(chunk, self.span))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Chunks {})
}
}

View File

@ -51,11 +51,11 @@ impl Command for Default {
description: description:
"Get the env value of `MY_ENV` with a default value 'abc' if not present", "Get the env value of `MY_ENV` with a default value 'abc' if not present",
example: "$env | get --ignore-errors MY_ENV | default 'abc'", example: "$env | get --ignore-errors MY_ENV | default 'abc'",
result: None, // Some(Value::test_string("abc")), result: Some(Value::test_string("abc")),
}, },
Example { Example {
description: "Replace the `null` value in a list", description: "Replace the `null` value in a list",
example: "[1, 2, null, 4] | default 3", example: "[1, 2, null, 4] | each { default 3 }",
result: Some(Value::list( result: Some(Value::list(
vec![ vec![
Value::test_int(1), Value::test_int(1),
@ -113,15 +113,7 @@ fn default(
} else if input.is_nothing() { } else if input.is_nothing() {
Ok(value.into_pipeline_data()) Ok(value.into_pipeline_data())
} else { } else {
input Ok(input)
.map(
move |item| match item {
Value::Nothing { .. } => value.clone(),
x => x,
},
engine_state.signals(),
)
.map(|x| x.set_metadata(metadata))
} }
} }

View File

@ -132,8 +132,6 @@ with 'transpose' first."#
Ok(data) => Some(data.into_value(head).unwrap_or_else(|err| { Ok(data) => Some(data.into_value(head).unwrap_or_else(|err| {
Value::error(chain_error_with_input(err, is_error, span), span) Value::error(chain_error_with_input(err, is_error, span), span)
})), })),
Err(ShellError::Continue { span }) => Some(Value::nothing(span)),
Err(ShellError::Break { .. }) => None,
Err(error) => { Err(error) => {
let error = chain_error_with_input(error, is_error, span); let error = chain_error_with_input(error, is_error, span);
Some(Value::error(error, span)) Some(Value::error(error, span))
@ -149,10 +147,6 @@ with 'transpose' first."#
.map_while(move |value| { .map_while(move |value| {
let value = match value { let value = match value {
Ok(value) => value, Ok(value) => value,
Err(ShellError::Continue { span }) => {
return Some(Value::nothing(span))
}
Err(ShellError::Break { .. }) => return None,
Err(err) => return Some(Value::error(err, head)), Err(err) => return Some(Value::error(err, head)),
}; };
@ -163,8 +157,6 @@ with 'transpose' first."#
.and_then(|data| data.into_value(head)) .and_then(|data| data.into_value(head))
{ {
Ok(value) => Some(value), Ok(value) => Some(value),
Err(ShellError::Continue { span }) => Some(Value::nothing(span)),
Err(ShellError::Break { .. }) => None,
Err(error) => { Err(error) => {
let error = chain_error_with_input(error, is_error, span); let error = chain_error_with_input(error, is_error, span);
Some(Value::error(error, span)) Some(Value::error(error, span))

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ValueIterator; use nu_protocol::{report_warning_new, ParseWarning, ValueIterator};
#[derive(Clone)] #[derive(Clone)]
pub struct Group; pub struct Group;
@ -54,6 +54,17 @@ impl Command for Group {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
report_warning_new(
engine_state,
&ParseWarning::DeprecatedWarning {
old_command: "group".into(),
new_suggestion: "the new `chunks` command".into(),
span: head,
url: "`help chunks`".into(),
},
);
let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?; let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?;
let metadata = input.metadata(); let metadata = input.metadata();

View File

@ -60,7 +60,6 @@ impl Command for Items {
match result { match result {
Ok(value) => Some(value), Ok(value) => Some(value),
Err(ShellError::Break { .. }) => None,
Err(err) => { Err(err) => {
let err = chain_error_with_input(err, false, span); let err = chain_error_with_input(err, false, span);
Some(Value::error(err, head)) Some(Value::error(err, head))

View File

@ -1,6 +1,7 @@
mod all; mod all;
mod any; mod any;
mod append; mod append;
mod chunks;
mod columns; mod columns;
mod compact; mod compact;
mod default; mod default;
@ -58,6 +59,7 @@ mod zip;
pub use all::All; pub use all::All;
pub use any::Any; pub use any::Any;
pub use append::Append; pub use append::Append;
pub use chunks::Chunks;
pub use columns::Columns; pub use columns::Columns;
pub use compact::Compact; pub use compact::Compact;
pub use default::Default; pub use default::Default;

View File

@ -36,7 +36,7 @@ impl Command for Reduce {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Aggregate a list to a single value using an accumulator closure." "Aggregate a list (starting from the left) to a single value using an accumulator closure."
} }
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {
@ -50,6 +50,11 @@ impl Command for Reduce {
description: "Sum values of a list (same as 'math sum')", description: "Sum values of a list (same as 'math sum')",
result: Some(Value::test_int(10)), result: Some(Value::test_int(10)),
}, },
Example {
example: "[ 1 2 3 4 ] | reduce {|it, acc| $acc - $it }",
description: r#"`reduce` accumulates value from left to right, equivalent to (((1 - 2) - 3) - 4)."#,
result: Some(Value::test_int(-8)),
},
Example { Example {
example: example:
"[ 8 7 6 ] | enumerate | reduce --fold 0 {|it, acc| $acc + $it.item + $it.index }", "[ 8 7 6 ] | enumerate | reduce --fold 0 {|it, acc| $acc + $it.item + $it.index }",
@ -61,6 +66,11 @@ impl Command for Reduce {
description: "Sum values with a starting value (fold)", description: "Sum values with a starting value (fold)",
result: Some(Value::test_int(20)), result: Some(Value::test_int(20)),
}, },
Example {
example: r#"[[foo baz] [baz quux]] | reduce --fold "foobar" {|it, acc| $acc | str replace $it.0 $it.1}"#,
description: "Iteratively perform string replace (from left to right): 'foobar' -> 'bazbar' -> 'quuxbar'",
result: Some(Value::test_string("quuxbar")),
},
Example { Example {
example: r#"[ i o t ] | reduce --fold "Arthur, King of the Britons" {|it, acc| $acc | str replace --all $it "X" }"#, example: r#"[ i o t ] | reduce --fold "Arthur, King of the Britons" {|it, acc| $acc | str replace --all $it "X" }"#,
description: "Replace selected characters in a string with 'X'", description: "Replace selected characters in a string with 'X'",
@ -110,8 +120,7 @@ impl Command for Reduce {
engine_state.signals().check(head)?; engine_state.signals().check(head)?;
acc = closure acc = closure
.add_arg(value) .add_arg(value)
.add_arg(acc) .run_with_value(acc)?
.run_with_input(PipelineData::Empty)?
.into_value(head)?; .into_value(head)?;
} }

View File

@ -240,7 +240,7 @@ fn select(
//FIXME: improve implementation to not clone //FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) { match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => { Ok(fetcher) => {
record.push(path.to_string().replace('.', "_"), fetcher); record.push(path.to_string(), fetcher);
if !columns_with_value.contains(&path) { if !columns_with_value.contains(&path) {
columns_with_value.push(path); columns_with_value.push(path);
} }
@ -271,7 +271,7 @@ fn select(
// FIXME: remove clone // FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) { match v.clone().follow_cell_path(&cell_path.members, false) {
Ok(result) => { Ok(result) => {
record.push(cell_path.to_string().replace('.', "_"), result); record.push(cell_path.to_string(), result);
} }
Err(e) => return Err(e), Err(e) => return Err(e),
} }
@ -295,7 +295,7 @@ fn select(
//FIXME: improve implementation to not clone //FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) { match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => { Ok(value) => {
record.push(path.to_string().replace('.', "_"), value); record.push(path.to_string(), value);
} }
Err(e) => return Err(e), Err(e) => return Err(e),
} }

View File

@ -1,5 +1,6 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ValueIterator; use nu_protocol::ListStream;
use std::num::NonZeroUsize;
#[derive(Clone)] #[derive(Clone)]
pub struct Window; pub struct Window;
@ -12,8 +13,8 @@ impl Command for Window {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("window") Signature::build("window")
.input_output_types(vec![( .input_output_types(vec![(
Type::List(Box::new(Type::Any)), Type::list(Type::Any),
Type::List(Box::new(Type::List(Box::new(Type::Any)))), Type::list(Type::list(Type::Any)),
)]) )])
.required("window_size", SyntaxShape::Int, "The size of each window.") .required("window_size", SyntaxShape::Int, "The size of each window.")
.named( .named(
@ -34,72 +35,41 @@ impl Command for Window {
"Creates a sliding window of `window_size` that slide by n rows/elements across input." "Creates a sliding window of `window_size` that slide by n rows/elements across input."
} }
fn extra_usage(&self) -> &str {
"This command will error if `window_size` or `stride` are negative or zero."
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
let stream_test_1 = vec![
Value::list(
vec![Value::test_int(1), Value::test_int(2)],
Span::test_data(),
),
Value::list(
vec![Value::test_int(2), Value::test_int(3)],
Span::test_data(),
),
Value::list(
vec![Value::test_int(3), Value::test_int(4)],
Span::test_data(),
),
];
let stream_test_2 = vec![
Value::list(
vec![Value::test_int(1), Value::test_int(2)],
Span::test_data(),
),
Value::list(
vec![Value::test_int(4), Value::test_int(5)],
Span::test_data(),
),
Value::list(
vec![Value::test_int(7), Value::test_int(8)],
Span::test_data(),
),
];
let stream_test_3 = vec![
Value::list(
vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
Span::test_data(),
),
Value::list(
vec![Value::test_int(4), Value::test_int(5)],
Span::test_data(),
),
];
vec![ vec![
Example { Example {
example: "[1 2 3 4] | window 2", example: "[1 2 3 4] | window 2",
description: "A sliding window of two elements", description: "A sliding window of two elements",
result: Some(Value::list( result: Some(Value::test_list(vec![
stream_test_1, Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
Span::test_data(), Value::test_list(vec![Value::test_int(2), Value::test_int(3)]),
)), Value::test_list(vec![Value::test_int(3), Value::test_int(4)]),
])),
}, },
Example { Example {
example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3", example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3",
description: "A sliding window of two elements, with a stride of 3", description: "A sliding window of two elements, with a stride of 3",
result: Some(Value::list( result: Some(Value::test_list(vec![
stream_test_2, Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
Span::test_data(), Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
)), Value::test_list(vec![Value::test_int(7), Value::test_int(8)]),
])),
}, },
Example { Example {
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder", example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking", description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
result: Some(Value::list( result: Some(Value::test_list(vec![
stream_test_3, Value::test_list(vec![
Span::test_data(), Value::test_int(1),
)), Value::test_int(2),
Value::test_int(3),
]),
Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
])),
}, },
] ]
} }
@ -112,116 +82,169 @@ impl Command for Window {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?; let window_size: Value = call.req(engine_state, stack, 0)?;
let metadata = input.metadata(); let stride: Option<Value> = call.get_flag(engine_state, stack, "stride")?;
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
let remainder = call.has_flag(engine_state, stack, "remainder")?; let remainder = call.has_flag(engine_state, stack, "remainder")?;
let stride = stride.unwrap_or(1); let size =
usize::try_from(window_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue {
span: window_size.span(),
})?;
//FIXME: add in support for external redirection when engine-q supports it generally let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
msg: "`window_size` cannot be zero".into(),
val_span: window_size.span(),
call_span: head,
})?;
let each_group_iterator = EachWindowIterator { let stride = if let Some(stride_val) = stride {
group_size: group_size.item, let stride = usize::try_from(stride_val.as_int()?).map_err(|_| {
input: Box::new(input.into_iter()), ShellError::NeedsPositiveValue {
span: head, span: stride_val.span(),
previous: None, }
stride, })?;
remainder,
NonZeroUsize::try_from(stride).map_err(|_| ShellError::IncorrectValue {
msg: "`stride` cannot be zero".into(),
val_span: stride_val.span(),
call_span: head,
})?
} else {
NonZeroUsize::MIN
}; };
Ok(each_group_iterator.into_pipeline_data_with_metadata( if remainder && size == stride {
head, super::chunks::chunks(engine_state, input, size, head)
engine_state.signals().clone(), } else if stride >= size {
metadata, match input {
)) PipelineData::Value(Value::List { vals, .. }, metadata) => {
let chunks = WindowGapIter::new(vals, size, stride, remainder, head);
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
Ok(PipelineData::ListStream(stream, metadata))
}
PipelineData::ListStream(stream, metadata) => {
let stream = stream
.modify(|iter| WindowGapIter::new(iter, size, stride, remainder, head));
Ok(PipelineData::ListStream(stream, metadata))
}
input => Err(input.unsupported_input_error("list", head)),
}
} else {
match input {
PipelineData::Value(Value::List { vals, .. }, metadata) => {
let chunks = WindowOverlapIter::new(vals, size, stride, remainder, head);
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
Ok(PipelineData::ListStream(stream, metadata))
}
PipelineData::ListStream(stream, metadata) => {
let stream = stream
.modify(|iter| WindowOverlapIter::new(iter, size, stride, remainder, head));
Ok(PipelineData::ListStream(stream, metadata))
}
input => Err(input.unsupported_input_error("list", head)),
}
}
} }
} }
struct EachWindowIterator { struct WindowOverlapIter<I: Iterator<Item = Value>> {
group_size: usize, iter: I,
input: ValueIterator, window: Vec<Value>,
span: Span,
previous: Option<Vec<Value>>,
stride: usize, stride: usize,
remainder: bool, remainder: bool,
span: Span,
} }
impl Iterator for EachWindowIterator { impl<I: Iterator<Item = Value>> WindowOverlapIter<I> {
fn new(
iter: impl IntoIterator<IntoIter = I>,
size: NonZeroUsize,
stride: NonZeroUsize,
remainder: bool,
span: Span,
) -> Self {
Self {
iter: iter.into_iter(),
window: Vec::with_capacity(size.into()),
stride: stride.into(),
remainder,
span,
}
}
}
impl<I: Iterator<Item = Value>> Iterator for WindowOverlapIter<I> {
type Item = Value; type Item = Value;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut group = self.previous.take().unwrap_or_else(|| { let len = if self.window.is_empty() {
let mut vec = Vec::new(); self.window.capacity()
// We default to a Vec of capacity size + stride as striding pushes n extra elements to the end
vec.try_reserve(self.group_size + self.stride).ok();
vec
});
let mut current_count = 0;
if group.is_empty() {
loop {
let item = self.input.next();
match item {
Some(v) => {
group.push(v);
current_count += 1;
if current_count >= self.group_size {
break;
}
}
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
} else { } else {
// our historic buffer is already full, so stride instead self.stride
};
loop { self.window.extend((&mut self.iter).take(len));
let item = self.input.next();
match item { if self.window.len() == self.window.capacity()
Some(v) => { || (self.remainder && !self.window.is_empty())
group.push(v); {
let mut next = Vec::with_capacity(self.window.len());
current_count += 1; next.extend(self.window.iter().skip(self.stride).cloned());
if current_count >= self.stride { let window = std::mem::replace(&mut self.window, next);
break; Some(Value::list(window, self.span))
} } else {
} None
None => {
if self.remainder {
break;
} else {
return None;
}
}
}
}
// We now have elements + stride in our group, and need to
// drop the skipped elements. Drain to preserve allocation and capacity
// Dropping this iterator consumes it.
group.drain(..self.stride.min(group.len()));
} }
}
}
if group.is_empty() { struct WindowGapIter<I: Iterator<Item = Value>> {
return None; iter: I,
size: usize,
skip: usize,
first: bool,
remainder: bool,
span: Span,
}
impl<I: Iterator<Item = Value>> WindowGapIter<I> {
fn new(
iter: impl IntoIterator<IntoIter = I>,
size: NonZeroUsize,
stride: NonZeroUsize,
remainder: bool,
span: Span,
) -> Self {
let size = size.into();
Self {
iter: iter.into_iter(),
size,
skip: stride.get() - size,
first: true,
remainder,
span,
} }
}
}
let return_group = group.clone(); impl<I: Iterator<Item = Value>> Iterator for WindowGapIter<I> {
self.previous = Some(group); type Item = Value;
Some(Value::list(return_group, self.span)) fn next(&mut self) -> Option<Self::Item> {
let mut window = Vec::with_capacity(self.size);
window.extend(
(&mut self.iter)
.skip(if self.first { 0 } else { self.skip })
.take(self.size),
);
self.first = false;
if window.len() == self.size || (self.remainder && !window.is_empty()) {
Some(Value::list(window, self.span))
} else {
None
}
} }
} }

View File

@ -12,12 +12,12 @@ impl Command for Generate {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("generate") Signature::build("generate")
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))]) .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
.required("initial", SyntaxShape::Any, "Initial value.")
.required( .required(
"closure", "closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"Generator function.", "Generator function.",
) )
.optional("initial", SyntaxShape::Any, "Initial value.")
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.category(Category::Generators) .category(Category::Generators)
} }
@ -41,7 +41,7 @@ used as the next argument to the closure, otherwise generation stops.
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
example: "generate 0 {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }}", example: "generate {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }} 0",
description: "Generate a sequence of numbers", description: "Generate a sequence of numbers",
result: Some(Value::list( result: Some(Value::list(
vec![ vec![
@ -57,10 +57,17 @@ used as the next argument to the closure, otherwise generation stops.
}, },
Example { Example {
example: example:
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }", "generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } [0, 1]",
description: "Generate a continuous stream of Fibonacci numbers", description: "Generate a continuous stream of Fibonacci numbers",
result: None, result: None,
}, },
Example {
example:
"generate {|fib=[0, 1]| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
description:
"Generate a continuous stream of Fibonacci numbers, using default parameters",
result: None,
},
] ]
} }
@ -72,15 +79,15 @@ used as the next argument to the closure, otherwise generation stops.
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let initial: Value = call.req(engine_state, stack, 0)?; let closure: Closure = call.req(engine_state, stack, 0)?;
let closure: Closure = call.req(engine_state, stack, 1)?; let initial: Option<Value> = call.opt(engine_state, stack, 1)?;
let block = engine_state.get_block(closure.block_id);
let mut closure = ClosureEval::new(engine_state, stack, closure); let mut closure = ClosureEval::new(engine_state, stack, closure);
// A type of Option<S> is used to represent state. Invocation // A type of Option<S> is used to represent state. Invocation
// will stop on None. Using Option<S> allows functions to output // will stop on None. Using Option<S> allows functions to output
// one final value before stopping. // one final value before stopping.
let mut state = Some(initial); let mut state = Some(get_initial_state(initial, &block.signature, call.head)?);
let iter = std::iter::from_fn(move || { let iter = std::iter::from_fn(move || {
let arg = state.take()?; let arg = state.take()?;
@ -170,6 +177,38 @@ used as the next argument to the closure, otherwise generation stops.
} }
} }
fn get_initial_state(
initial: Option<Value>,
signature: &Signature,
span: Span,
) -> Result<Value, ShellError> {
match initial {
Some(v) => Ok(v),
None => {
// the initial state should be referred from signature
if !signature.optional_positional.is_empty()
&& signature.optional_positional[0].default_value.is_some()
{
Ok(signature.optional_positional[0]
.default_value
.clone()
.expect("Already checked default value"))
} else {
Err(ShellError::GenericError {
error: "The initial value is missing".to_string(),
msg: "Missing initial value".to_string(),
span: Some(span),
help: Some(
"Provide an <initial> value as an argument to generate, or assign a default value to the closure parameter"
.to_string(),
),
inner: vec![],
})
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod bytes; mod bytes;
mod charting; mod charting;
mod conversions; mod conversions;

View File

@ -29,6 +29,10 @@ impl Command for SubCommand {
vec!["square", "root"] vec!["square", "root"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -44,6 +48,23 @@ impl Command for SubCommand {
input.map(move |value| operate(value, head), engine_state.signals()) input.map(move |value| operate(value, head), engine_state.signals())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| operate(value, head),
working_set.permanent().signals(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Compute the square root of each number in a list", description: "Compute the square root of each number in a list",

View File

@ -141,17 +141,17 @@ pub fn request_add_authorization_header(
let login = match (user, password) { let login = match (user, password) {
(Some(user), Some(password)) => { (Some(user), Some(password)) => {
let mut enc_str = String::new(); let mut enc_str = String::new();
base64_engine.encode_string(&format!("{user}:{password}"), &mut enc_str); base64_engine.encode_string(format!("{user}:{password}"), &mut enc_str);
Some(enc_str) Some(enc_str)
} }
(Some(user), _) => { (Some(user), _) => {
let mut enc_str = String::new(); let mut enc_str = String::new();
base64_engine.encode_string(&format!("{user}:"), &mut enc_str); base64_engine.encode_string(format!("{user}:"), &mut enc_str);
Some(enc_str) Some(enc_str)
} }
(_, Some(password)) => { (_, Some(password)) => {
let mut enc_str = String::new(); let mut enc_str = String::new();
base64_engine.encode_string(&format!(":{password}"), &mut enc_str); base64_engine.encode_string(format!(":{password}"), &mut enc_str);
Some(enc_str) Some(enc_str)
} }
_ => None, _ => None,

View File

@ -15,7 +15,11 @@ impl Command for SubCommand {
Signature::build("random int") Signature::build("random int")
.input_output_types(vec![(Type::Nothing, Type::Int)]) .input_output_types(vec![(Type::Nothing, Type::Int)])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.optional("range", SyntaxShape::Range, "Range of values.") .optional(
"range",
SyntaxShape::Range,
"Range of potential values, inclusive of both start and end values.",
)
.category(Category::Random) .category(Category::Random)
} }
@ -40,12 +44,12 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Generate an unconstrained random integer", description: "Generate a non-negative random integer",
example: "random int", example: "random int",
result: None, result: None,
}, },
Example { Example {
description: "Generate a random integer less than or equal to 500", description: "Generate a random integer between 0 (inclusive) and 500 (inclusive)",
example: "random int ..500", example: "random int ..500",
result: None, result: None,
}, },
@ -55,8 +59,8 @@ impl Command for SubCommand {
result: None, result: None,
}, },
Example { Example {
description: "Generate a random integer between 1 and 10", description: "Generate a random integer between -10 (inclusive) and 10 (inclusive)",
example: "random int 1..10", example: "random int (-10)..10",
result: None, result: None,
}, },
] ]

View File

@ -3,6 +3,7 @@ use nu_engine::command_prelude::*;
use nu_protocol::Signals; use nu_protocol::Signals;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashSet;
// Character used to separate directories in a Path Environment variable on windows is ";" // Character used to separate directories in a Path Environment variable on windows is ";"
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
@ -149,6 +150,24 @@ static CHAR_MAP: Lazy<IndexMap<&'static str, String>> = Lazy::new(|| {
} }
}); });
static NO_OUTPUT_CHARS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
[
// If the character is in the this set, we don't output it to prevent
// the broken of `char --list` command table format and alignment.
"newline",
"enter",
"nl",
"line_feed",
"lf",
"cr",
"crlf",
"bel",
"backspace",
]
.into_iter()
.collect()
});
impl Command for Char { impl Command for Char {
fn name(&self) -> &str { fn name(&self) -> &str {
"char" "char"
@ -297,6 +316,11 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
CHAR_MAP CHAR_MAP
.iter() .iter()
.map(move |(name, s)| { .map(move |(name, s)| {
let character = if NO_OUTPUT_CHARS.contains(name) {
Value::string("", call_span)
} else {
Value::string(s, call_span)
};
let unicode = Value::string( let unicode = Value::string(
s.chars() s.chars()
.map(|c| format!("{:x}", c as u32)) .map(|c| format!("{:x}", c as u32))
@ -306,7 +330,7 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
); );
let record = record! { let record = record! {
"name" => Value::string(*name, call_span), "name" => Value::string(*name, call_span),
"character" => Value::string(s, call_span), "character" => character,
"unicode" => unicode, "unicode" => unicode,
}; };

View File

@ -187,7 +187,7 @@ fn split_words_helper(v: &Value, word_length: Option<usize>, span: Span, graphem
// [^[:alpha:]\'] = do not match any uppercase or lowercase letters or apostrophes // [^[:alpha:]\'] = do not match any uppercase or lowercase letters or apostrophes
// [^\p{L}\'] = do not match any unicode uppercase or lowercase letters or apostrophes // [^\p{L}\'] = do not match any unicode uppercase or lowercase letters or apostrophes
// Let's go with the unicode one in hopes that it works on more than just ascii characters // Let's go with the unicode one in hopes that it works on more than just ascii characters
let regex_replace = Regex::new(r"[^\p{L}\']").expect("regular expression error"); let regex_replace = Regex::new(r"[^\p{L}\p{N}\']").expect("regular expression error");
let v_span = v.span(); let v_span = v.span();
match v { match v {
@ -422,4 +422,9 @@ mod test {
test_examples(SubCommand {}) test_examples(SubCommand {})
} }
#[test]
fn mixed_letter_number() {
let actual = nu!(r#"echo "a1 b2 c3" | split words | str join ','"#);
assert_eq!(actual.out, "a1,b2,c3");
}
} }

View File

@ -606,7 +606,7 @@ mod test {
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let home = dirs_next::home_dir().expect("failed to get home dir"); let home = dirs::home_dir().expect("failed to get home dir");
let expected: Vec<OsString> = vec![home.join("foo.txt").into()]; let expected: Vec<OsString> = vec![home.join("foo.txt").into()];
assert_eq!(actual, expected); assert_eq!(actual, expected);
}) })

View File

@ -15,12 +15,3 @@ fn break_while_loop() {
assert_eq!(actual.out, "hello"); assert_eq!(actual.out, "hello");
} }
#[test]
fn break_each() {
let actual = nu!("
[1, 2, 3, 4, 5] | each {|x| if $x > 3 { break }; $x} | math sum
");
assert_eq!(actual.out, "6");
}

View File

@ -151,7 +151,7 @@ fn filesystem_change_to_home_directory() {
" "
); );
assert_eq!(Some(PathBuf::from(actual.out)), dirs_next::home_dir()); assert_eq!(Some(PathBuf::from(actual.out)), dirs::home_dir());
}) })
} }

View File

@ -0,0 +1,43 @@
use nu_test_support::nu;
#[test]
fn chunk_size_negative() {
let actual = nu!("[0 1 2] | chunks -1");
assert!(actual.err.contains("positive"));
}
#[test]
fn chunk_size_zero() {
let actual = nu!("[0 1 2] | chunks 0");
assert!(actual.err.contains("zero"));
}
#[test]
fn chunk_size_not_int() {
let actual = nu!("[0 1 2] | chunks (if true { 1sec })");
assert!(actual.err.contains("can't convert"));
}
#[test]
fn empty() {
let actual = nu!("[] | chunks 2 | is-empty");
assert_eq!(actual.out, "true");
}
#[test]
fn list_stream() {
let actual = nu!("([0 1 2] | every 1 | chunks 2) == ([0 1 2] | chunks 2)");
assert_eq!(actual.out, "true");
}
#[test]
fn table_stream() {
let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | chunks 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | chunks 2)");
assert_eq!(actual.out, "true");
}
#[test]
fn no_empty_chunks() {
let actual = nu!("([0 1 2 3 4 5] | chunks 3 | length) == 2");
assert_eq!(actual.out, "true");
}

View File

@ -95,13 +95,13 @@ fn capture_error_with_both_stdout_stderr_messages_not_hang_nushell() {
#[test] #[test]
fn combined_pipe_redirection() { fn combined_pipe_redirection() {
let actual = nu!("$env.FOO = hello; $env.BAR = world; nu --testbin echo_env_mixed out-err FOO BAR o+e>| complete | get stdout"); let actual = nu!("$env.FOO = 'hello'; $env.BAR = 'world'; nu --testbin echo_env_mixed out-err FOO BAR o+e>| complete | get stdout");
assert_eq!(actual.out, "helloworld"); assert_eq!(actual.out, "helloworld");
} }
#[test] #[test]
fn err_pipe_redirection() { fn err_pipe_redirection() {
let actual = let actual =
nu!("$env.FOO = hello; nu --testbin echo_env_stderr FOO e>| complete | get stdout"); nu!("$env.FOO = 'hello'; nu --testbin echo_env_stderr FOO e>| complete | get stdout");
assert_eq!(actual.out, "hello"); assert_eq!(actual.out, "hello");
} }

View File

@ -32,3 +32,15 @@ fn default_after_empty_filter() {
assert_eq!(actual.out, "d"); assert_eq!(actual.out, "d");
} }
#[test]
fn keeps_nulls_in_lists() {
let actual = nu!(r#"[null, 2, 3] | default [] | to json -r"#);
assert_eq!(actual.out, "[null,2,3]");
}
#[test]
fn replaces_null() {
let actual = nu!(r#"null | default 1"#);
assert_eq!(actual.out, "1");
}

View File

@ -58,22 +58,6 @@ fn each_while_uses_enumerate_index() {
assert_eq!(actual.out, "[0, 1, 2, 3]"); assert_eq!(actual.out, "[0, 1, 2, 3]");
} }
#[test]
fn each_element_continue_command() {
let actual =
nu!("[1,2,3,4,6,7] | each { |x| if ($x mod 2 == 0) {continue} else { $x }} | to nuon");
assert_eq!(actual.out, "[1, 3, 7]");
}
#[test]
fn each_element_break_command() {
let actual =
nu!("[1,2,5,4,6,7] | each { |x| if ($x mod 3 == 0) {break} else { $x }} | to nuon");
assert_eq!(actual.out, "[1, 2, 5, 4]");
}
#[test] #[test]
fn errors_in_nested_each_show() { fn errors_in_nested_each_show() {
let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }"); let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }");

View File

@ -3,7 +3,7 @@ use nu_test_support::{nu, pipeline};
#[test] #[test]
fn generate_no_next_break() { fn generate_no_next_break() {
let actual = nu!( let actual = nu!(
"generate 1 {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} | to nuon" "generate {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} 1 | to nuon"
); );
assert_eq!(actual.out, "[1, 2, 3]"); assert_eq!(actual.out, "[1, 2, 3]");
@ -11,7 +11,7 @@ fn generate_no_next_break() {
#[test] #[test]
fn generate_null_break() { fn generate_null_break() {
let actual = nu!("generate 1 {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} | to nuon"); let actual = nu!("generate {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} 1 | to nuon");
assert_eq!(actual.out, "[1, 2, 3]"); assert_eq!(actual.out, "[1, 2, 3]");
} }
@ -20,13 +20,13 @@ fn generate_null_break() {
fn generate_allows_empty_output() { fn generate_allows_empty_output() {
let actual = nu!(pipeline( let actual = nu!(pipeline(
r#" r#"
generate 0 {|x| generate {|x|
if $x == 1 { if $x == 1 {
{next: ($x + 1)} {next: ($x + 1)}
} else if $x < 3 { } else if $x < 3 {
{out: $x, next: ($x + 1)} {out: $x, next: ($x + 1)}
} }
} | to nuon } 0 | to nuon
"# "#
)); ));
@ -37,11 +37,11 @@ fn generate_allows_empty_output() {
fn generate_allows_no_output() { fn generate_allows_no_output() {
let actual = nu!(pipeline( let actual = nu!(pipeline(
r#" r#"
generate 0 {|x| generate {|x|
if $x < 3 { if $x < 3 {
{next: ($x + 1)} {next: ($x + 1)}
} }
} | to nuon } 0 | to nuon
"# "#
)); ));
@ -52,7 +52,7 @@ fn generate_allows_no_output() {
fn generate_allows_null_state() { fn generate_allows_null_state() {
let actual = nu!(pipeline( let actual = nu!(pipeline(
r#" r#"
generate 0 {|x| generate {|x|
if $x == null { if $x == null {
{out: "done"} {out: "done"}
} else if $x < 1 { } else if $x < 1 {
@ -60,7 +60,7 @@ fn generate_allows_null_state() {
} else { } else {
{out: "stopping", next: null} {out: "stopping", next: null}
} }
} | to nuon } 0 | to nuon
"# "#
)); ));
@ -71,7 +71,42 @@ fn generate_allows_null_state() {
fn generate_allows_null_output() { fn generate_allows_null_output() {
let actual = nu!(pipeline( let actual = nu!(pipeline(
r#" r#"
generate 0 {|x| generate {|x|
if $x == 3 {
{out: "done"}
} else {
{out: null, next: ($x + 1)}
}
} 0 | to nuon
"#
));
assert_eq!(actual.out, "[null, null, null, done]");
}
#[test]
fn generate_disallows_extra_keys() {
let actual = nu!("generate {|x| {foo: bar, out: $x}} 0 ");
assert!(actual.err.contains("Invalid block return"));
}
#[test]
fn generate_disallows_list() {
let actual = nu!("generate {|x| [$x, ($x + 1)]} 0 ");
assert!(actual.err.contains("Invalid block return"));
}
#[test]
fn generate_disallows_primitive() {
let actual = nu!("generate {|x| 1} 0");
assert!(actual.err.contains("Invalid block return"));
}
#[test]
fn generate_allow_default_parameter() {
let actual = nu!(pipeline(
r#"
generate {|x = 0|
if $x == 3 { if $x == 3 {
{out: "done"} {out: "done"}
} else { } else {
@ -82,22 +117,34 @@ fn generate_allows_null_output() {
)); ));
assert_eq!(actual.out, "[null, null, null, done]"); assert_eq!(actual.out, "[null, null, null, done]");
// if initial is given, use initial value
let actual = nu!(pipeline(
r#"
generate {|x = 0|
if $x == 3 {
{out: "done"}
} else {
{out: null, next: ($x + 1)}
}
} 1 | to nuon
"#
));
assert_eq!(actual.out, "[null, null, done]");
} }
#[test] #[test]
fn generate_disallows_extra_keys() { fn generate_raise_error_on_no_default_parameter_closure_and_init_val() {
let actual = nu!("generate 0 {|x| {foo: bar, out: $x}}"); let actual = nu!(pipeline(
assert!(actual.err.contains("Invalid block return")); r#"
} generate {|x|
if $x == 3 {
#[test] {out: "done"}
fn generate_disallows_list() { } else {
let actual = nu!("generate 0 {|x| [$x, ($x + 1)]}"); {out: null, next: ($x + 1)}
assert!(actual.err.contains("Invalid block return")); }
} } | to nuon
"#
#[test] ));
fn generate_disallows_primitive() { assert!(actual.err.contains("The initial value is missing"));
let actual = nu!("generate 0 {|x| 1}");
assert!(actual.err.contains("Invalid block return"));
} }

View File

@ -23,6 +23,13 @@ fn let_takes_pipeline() {
assert_eq!(actual.out, "11"); assert_eq!(actual.out, "11");
} }
#[test]
fn let_takes_pipeline_with_declared_type() {
let actual = nu!(r#"let x: list<string> = [] | append "hello world"; print $x.0"#);
assert_eq!(actual.out, "hello world");
}
#[test] #[test]
fn let_pipeline_allows_in() { fn let_pipeline_allows_in() {
let actual = let actual =
@ -38,6 +45,13 @@ fn mut_takes_pipeline() {
assert_eq!(actual.out, "11"); assert_eq!(actual.out, "11");
} }
#[test]
fn mut_takes_pipeline_with_declared_type() {
let actual = nu!(r#"mut x: list<string> = [] | append "hello world"; print $x.0"#);
assert_eq!(actual.out, "hello world");
}
#[test] #[test]
fn mut_pipeline_allows_in() { fn mut_pipeline_allows_in() {
let actual = let actual =

View File

@ -7,6 +7,7 @@ mod break_;
mod bytes; mod bytes;
mod cal; mod cal;
mod cd; mod cd;
mod chunks;
mod compact; mod compact;
mod complete; mod complete;
mod config_env_default; mod config_env_default;
@ -114,6 +115,7 @@ mod try_;
mod ucp; mod ucp;
#[cfg(unix)] #[cfg(unix)]
mod ulimit; mod ulimit;
mod window;
mod debug; mod debug;
mod umkdir; mod umkdir;

View File

@ -3,5 +3,4 @@ mod chars;
mod dice; mod dice;
mod float; mod float;
mod int; mod int;
#[cfg(feature = "uuid_crate")]
mod uuid; mod uuid;

View File

@ -1,5 +1,5 @@
use nu_test_support::nu; use nu_test_support::nu;
use uuid_crate::Uuid; use uuid::Uuid;
#[test] #[test]
fn generates_valid_uuid4() { fn generates_valid_uuid4() {

View File

@ -439,3 +439,37 @@ fn no_duplicate_redirection() {
); );
}); });
} }
#[rstest::rstest]
#[case("let", "out>")]
#[case("let", "err>")]
#[case("let", "out+err>")]
#[case("mut", "out>")]
#[case("mut", "err>")]
#[case("mut", "out+err>")]
fn file_redirection_in_let_and_mut(#[case] keyword: &str, #[case] redir: &str) {
Playground::setup("file redirection in let and mut", |dirs, _| {
let actual = nu!(
cwd: dirs.test(),
format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} result.txt")
);
assert!(actual.status.success());
assert!(file_contents(dirs.test().join("result.txt")).contains("foo"));
})
}
#[rstest::rstest]
#[case("let", "err>|", "foo3")]
#[case("let", "out+err>|", "7")]
#[case("mut", "err>|", "foo3")]
#[case("mut", "out+err>|", "7")]
fn pipe_redirection_in_let_and_mut(
#[case] keyword: &str,
#[case] redir: &str,
#[case] output: &str,
) {
let actual = nu!(
format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} str length; $v")
);
assert_eq!(actual.out, output);
}

View File

@ -1,8 +1,10 @@
#[cfg(not(windows))]
use nu_path::AbsolutePath; use nu_path::AbsolutePath;
use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; use nu_test_support::fs::{files_exist_at, Stub::EmptyFile};
use nu_test_support::nu; use nu_test_support::nu;
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
use rstest::rstest; use rstest::rstest;
#[cfg(not(windows))]
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
@ -144,7 +146,7 @@ fn errors_if_attempting_to_delete_home() {
Playground::setup("rm_test_8", |dirs, _| { Playground::setup("rm_test_8", |dirs, _| {
let actual = nu!( let actual = nu!(
cwd: dirs.root(), cwd: dirs.root(),
"$env.HOME = myhome ; rm -rf ~" "$env.HOME = 'myhome' ; rm -rf ~"
); );
assert!(actual.err.contains("please use -I or -i")); assert!(actual.err.contains("please use -I or -i"));
@ -405,16 +407,19 @@ fn removes_file_after_cd() {
}) })
} }
#[cfg(not(windows))]
struct Cleanup<'a> { struct Cleanup<'a> {
dir_to_clean: &'a AbsolutePath, dir_to_clean: &'a AbsolutePath,
} }
#[cfg(not(windows))]
fn set_dir_read_only(directory: &AbsolutePath, read_only: bool) { fn set_dir_read_only(directory: &AbsolutePath, read_only: bool) {
let mut permissions = fs::metadata(directory).unwrap().permissions(); let mut permissions = fs::metadata(directory).unwrap().permissions();
permissions.set_readonly(read_only); permissions.set_readonly(read_only);
fs::set_permissions(directory, permissions).expect("failed to set directory permissions"); fs::set_permissions(directory, permissions).expect("failed to set directory permissions");
} }
#[cfg(not(windows))]
impl<'a> Drop for Cleanup<'a> { impl<'a> Drop for Cleanup<'a> {
/// Restores write permissions to the given directory so that the Playground can be successfully /// Restores write permissions to the given directory so that the Playground can be successfully
/// cleaned up. /// cleaned up.

View File

@ -309,7 +309,7 @@ fn external_arg_expand_tilde() {
"# "#
)); ));
let home = dirs_next::home_dir().expect("failed to find home dir"); let home = dirs::home_dir().expect("failed to find home dir");
assert_eq!( assert_eq!(
actual.out, actual.out,

View File

@ -463,3 +463,65 @@ fn save_same_file_with_collect_and_filter() {
assert_eq!("helloworld", actual.out); assert_eq!("helloworld", actual.out);
}) })
} }
#[test]
fn save_from_child_process_dont_sink_stderr() {
Playground::setup("save_test_22", |dirs, sandbox| {
sandbox.with_files(&[
Stub::FileWithContent("log.txt", "Old"),
Stub::FileWithContent("err.txt", "Old Err"),
]);
let expected_file = dirs.test().join("log.txt");
let expected_stderr_file = dirs.test().join("err.txt");
let actual = nu!(
cwd: dirs.root(),
r#"
$env.FOO = " New";
$env.BAZ = " New Err";
do -i {nu -n -c 'nu --testbin echo_env FOO; nu --testbin echo_env_stderr BAZ'} | save -a -r save_test_22/log.txt"#,
);
assert_eq!(actual.err.trim_end(), " New Err");
let actual = file_contents(expected_file);
assert_eq!(actual.trim_end(), "Old New");
let actual = file_contents(expected_stderr_file);
assert_eq!(actual.trim_end(), "Old Err");
})
}
#[test]
fn parent_redirection_doesnt_affect_save() {
Playground::setup("save_test_23", |dirs, sandbox| {
sandbox.with_files(&[
Stub::FileWithContent("log.txt", "Old"),
Stub::FileWithContent("err.txt", "Old Err"),
]);
let expected_file = dirs.test().join("log.txt");
let expected_stderr_file = dirs.test().join("err.txt");
let actual = nu!(
cwd: dirs.root(),
r#"
$env.FOO = " New";
$env.BAZ = " New Err";
def tttt [] {
do -i {nu -n -c 'nu --testbin echo_env FOO; nu --testbin echo_env_stderr BAZ'} | save -a -r save_test_23/log.txt
};
tttt e> ("save_test_23" | path join empty_file)"#
);
assert_eq!(actual.err.trim_end(), " New Err");
let actual = file_contents(expected_file);
assert_eq!(actual.trim_end(), "Old New");
let actual = file_contents(expected_stderr_file);
assert_eq!(actual.trim_end(), "Old Err");
let actual = file_contents(dirs.test().join("empty_file"));
assert_eq!(actual.trim_end(), "");
})
}

View File

@ -50,7 +50,7 @@ fn complex_nested_columns() {
r#" r#"
{sample} {sample}
| select nu."0xATYKARNU" nu.committers.name nu.releases.version | select nu."0xATYKARNU" nu.committers.name nu.releases.version
| get nu_releases_version | get "nu.releases.version"
| where $it > "0.8" | where $it > "0.8"
| get 0 | get 0
"# "#

View File

@ -2567,7 +2567,7 @@ fn theme_cmd(theme: &str, footer: bool, then: &str) -> String {
with_footer = "$env.config.footer_mode = \"always\"".to_string(); with_footer = "$env.config.footer_mode = \"always\"".to_string();
} }
format!("$env.config.table.mode = {theme}; $env.config.table.header_on_separator = true; {with_footer}; {then}") format!("$env.config.table.mode = \"{theme}\"; $env.config.table.header_on_separator = true; {with_footer}; {then}")
} }
#[test] #[test]
@ -2901,3 +2901,9 @@ fn table_general_header_on_separator_trim_algorithm() {
let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic"); let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic");
assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+"); assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+");
} }
#[test]
fn table_general_header_on_separator_issue1() {
let actual = nu!("$env.config.table.header_on_separator = true; [['Llll oo Bbbbbbbb' 'Bbbbbbbb Aaaa' Nnnnnn Ggggg 'Xxxxx Llllllll #' Bbb 'Pppp Ccccc' 'Rrrrrrrr Dddd' Rrrrrr 'Rrrrrr Ccccc II' 'Rrrrrr Ccccc Ppppppp II' 'Pppppp Dddddddd Tttt' 'Pppppp Dddddddd Dddd' 'Rrrrrrrrr Trrrrrr' 'Pppppp Ppppp Dddd' 'Ppppp Dddd' Hhhh]; [RRRRRRR FFFFFFFF UUUU VV 202407160001 BBB 1 '7/16/2024' '' AAA-1111 AAA-1111-11 '7 YEARS' 2555 'RRRRRRRR DDDD' '7/16/2031' '7/16/2031' NN]] | table --width=87 --theme basic");
assert_eq!(actual.out, "+-#-+-Llll oo Bbbbbbbb-+-Bbbbbbbb Aaaa-+-Nnnnnn-+-Ggggg-+-Xxxxx Llllllll #-+-...-+| 0 | RRRRRRR | FFFFFFFF | UUUU | VV | 202407160001 | ... |+---+------------------+---------------+--------+-------+------------------+-----+");
}

View File

@ -0,0 +1,103 @@
use nu_test_support::nu;
#[test]
fn window_size_negative() {
let actual = nu!("[0 1 2] | window -1");
assert!(actual.err.contains("positive"));
}
#[test]
fn window_size_zero() {
let actual = nu!("[0 1 2] | window 0");
assert!(actual.err.contains("zero"));
}
#[test]
fn window_size_not_int() {
let actual = nu!("[0 1 2] | window (if true { 1sec })");
assert!(actual.err.contains("can't convert"));
}
#[test]
fn stride_negative() {
let actual = nu!("[0 1 2] | window 1 -s -1");
assert!(actual.err.contains("positive"));
}
#[test]
fn stride_zero() {
let actual = nu!("[0 1 2] | window 1 -s 0");
assert!(actual.err.contains("zero"));
}
#[test]
fn stride_not_int() {
let actual = nu!("[0 1 2] | window 1 -s (if true { 1sec })");
assert!(actual.err.contains("can't convert"));
}
#[test]
fn empty() {
let actual = nu!("[] | window 2 | is-empty");
assert_eq!(actual.out, "true");
}
#[test]
fn list_stream() {
let actual = nu!("([0 1 2] | every 1 | window 2) == ([0 1 2] | window 2)");
assert_eq!(actual.out, "true");
}
#[test]
fn table_stream() {
let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | window 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | window 2)");
assert_eq!(actual.out, "true");
}
#[test]
fn no_empty_chunks() {
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 3 -r | length) == 2");
assert_eq!(actual.out, "true");
}
#[test]
fn same_as_chunks() {
let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r) == ([0 1 2 3 4 ] | chunks 2)");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_equal_to_window_size() {
let actual = nu!("([0 1 2 3] | window 2 -s 2 | flatten) == [0 1 2 3]");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_greater_than_window_size() {
let actual = nu!("([0 1 2 3 4] | window 2 -s 3 | flatten) == [0 1 3 4]");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_less_than_window_size() {
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 | length) == 2");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_equal_to_window_size_remainder() {
let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r | flatten) == [0 1 2 3 4]");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_greater_than_window_size_remainder() {
let actual = nu!("([0 1 2 3 4] | window 2 -s 3 -r | flatten) == [0 1 3 4]");
assert_eq!(actual.out, "true");
}
#[test]
fn stride_less_than_window_size_remainder() {
let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 -r | length) == 3");
assert_eq!(actual.out, "true");
}

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-derive-value" name = "nu-derive-value"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-engine" name = "nu-engine"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" } nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.95.1" } nu-path = { path = "../nu-path", version = "0.96.2" }
nu-glob = { path = "../nu-glob", version = "0.95.1" } nu-glob = { path = "../nu-glob", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
log = { workspace = true } log = { workspace = true }
[features] [features]
plugin = [] plugin = []

View File

@ -0,0 +1,9 @@
This crate primarily drives the evaluation of expressions.
(Some overlap with nu-protocol)
- Provides `CallExt`
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -204,6 +204,7 @@ impl BlockBuilder {
Instruction::Drain { src } => allocate(&[*src], &[]), Instruction::Drain { src } => allocate(&[*src], &[]),
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]), Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]), Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
Instruction::DropVariable { var_id: _ } => Ok(()),
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]), Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]), Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]), Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
@ -422,7 +423,7 @@ impl BlockBuilder {
self.push(Instruction::Jump { index: label_id.0 }.into_spanned(span)) self.push(Instruction::Jump { index: label_id.0 }.into_spanned(span))
} }
/// The index that the next instruction [`.push()`]ed will have. /// The index that the next instruction [`.push()`](Self::push)ed will have.
pub(crate) fn here(&self) -> usize { pub(crate) fn here(&self) -> usize {
self.instructions.len() self.instructions.len()
} }

View File

@ -171,6 +171,27 @@ pub(crate) fn compile_expression(
Err(CompileError::UnsupportedOperatorExpression { span: op.span }) Err(CompileError::UnsupportedOperatorExpression { span: op.span })
} }
} }
Expr::Collect(var_id, expr) => {
let store_reg = if let Some(in_reg) = in_reg {
// Collect, clone, store
builder.push(Instruction::Collect { src_dst: in_reg }.into_spanned(expr.span))?;
builder.clone_reg(in_reg, expr.span)?
} else {
// Just store nothing in the variable
builder.literal(Literal::Nothing.into_spanned(Span::unknown()))?
};
builder.push(
Instruction::StoreVariable {
var_id: *var_id,
src: store_reg,
}
.into_spanned(expr.span),
)?;
compile_expression(working_set, builder, expr, redirect_modes, in_reg, out_reg)?;
// Clean it up afterward
builder.push(Instruction::DropVariable { var_id: *var_id }.into_spanned(expr.span))?;
Ok(())
}
Expr::Subexpression(block_id) => { Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg) compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
@ -423,7 +444,15 @@ pub(crate) fn compile_expression(
working_set, working_set,
builder, builder,
&full_cell_path.head, &full_cell_path.head,
RedirectModes::capture_out(expr.span), // Only capture the output if there is a tail. This was a bit of a headscratcher
// as the parser emits a FullCellPath with no tail for subexpressions in
// general, which shouldn't be captured any differently than they otherwise
// would be.
if !full_cell_path.tail.is_empty() {
RedirectModes::capture_out(expr.span)
} else {
redirect_modes
},
in_reg, in_reg,
out_reg, out_reg,
)?; )?;

View File

@ -10,8 +10,8 @@ use nu_protocol::{
debugger::DebugContext, debugger::DebugContext,
engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet}, engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
eval_base::Eval, eval_base::Eval,
ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span, ByteStreamSource, Config, DataSource, FromValue, IntoPipelineData, OutDest, PipelineData,
Spanned, Type, Value, VarId, ENV_VARIABLE_ID, PipelineMetadata, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
}; };
use nu_utils::IgnoreCaseExt; use nu_utils::IgnoreCaseExt;
use std::{fs::OpenOptions, path::PathBuf, sync::Arc}; use std::{fs::OpenOptions, path::PathBuf, sync::Arc};
@ -198,7 +198,7 @@ pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee
} }
// set config to callee config, to capture any updates to that // set config to callee config, to capture any updates to that
caller_stack.config = callee_stack.config.clone(); caller_stack.config.clone_from(&callee_stack.config);
} }
fn eval_external( fn eval_external(
@ -259,6 +259,10 @@ pub fn eval_expression_with_input<D: DebugContext>(
input = eval_external(engine_state, stack, head, args, input)?; input = eval_external(engine_state, stack, head, args, input)?;
} }
Expr::Collect(var_id, expr) => {
input = eval_collect::<D>(engine_state, stack, *var_id, expr, input)?;
}
Expr::Subexpression(block_id) => { Expr::Subexpression(block_id) => {
let block = engine_state.get_block(*block_id); let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c // FIXME: protect this collect with ctrl-c
@ -605,6 +609,44 @@ pub fn eval_block<D: DebugContext>(
Ok(input) Ok(input)
} }
pub fn eval_collect<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
var_id: VarId,
expr: &Expression,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// Evaluate the expression with the variable set to the collected input
let span = input.span().unwrap_or(Span::unknown());
let metadata = match input.metadata() {
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
// check where some input came from.
Some(PipelineMetadata {
data_source: DataSource::FilePath(_),
content_type: None,
}) => None,
other => other,
};
let input = input.into_value(span)?;
stack.add_var(var_id, input.clone());
let result = eval_expression_with_input::<D>(
engine_state,
stack,
expr,
// We still have to pass it as input
input.into_pipeline_data_with_metadata(metadata),
)
.map(|(result, _failed)| result);
stack.remove_var(var_id);
result
}
pub fn eval_subexpression<D: DebugContext>( pub fn eval_subexpression<D: DebugContext>(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
@ -729,6 +771,18 @@ impl Eval for EvalRuntime {
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span) eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
} }
fn eval_collect<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
var_id: VarId,
expr: &Expression,
) -> Result<Value, ShellError> {
// It's a little bizarre, but the expression can still have some kind of result even with
// nothing input
eval_collect::<D>(engine_state, stack, var_id, expr, PipelineData::empty())?
.into_value(expr.span)
}
fn eval_subexpression<D: DebugContext>( fn eval_subexpression<D: DebugContext>(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,

View File

@ -6,9 +6,9 @@ use nu_protocol::{
debugger::DebugContext, debugger::DebugContext,
engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack}, engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack},
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, record, ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned,
OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature, ListStream, OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId,
Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, ShellError, Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
}; };
use nu_utils::IgnoreCaseExt; use nu_utils::IgnoreCaseExt;
@ -184,11 +184,20 @@ fn eval_ir_block_impl<D: DebugContext>(
let instruction = &ir_block.instructions[pc]; let instruction = &ir_block.instructions[pc];
let span = &ir_block.spans[pc]; let span = &ir_block.spans[pc];
let ast = &ir_block.ast[pc]; let ast = &ir_block.ast[pc];
log::trace!(
"{pc:-4}: {}", D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers);
instruction.display(ctx.engine_state, ctx.data)
let result = eval_instruction::<D>(ctx, instruction, span, ast);
D::leave_instruction(
ctx.engine_state,
ir_block,
pc,
ctx.registers,
result.as_ref().err(),
); );
match eval_instruction::<D>(ctx, instruction, span, ast) {
match result {
Ok(InstructionResult::Continue) => { Ok(InstructionResult::Continue) => {
pc += 1; pc += 1;
} }
@ -336,6 +345,10 @@ fn eval_instruction<D: DebugContext>(
ctx.stack.add_var(*var_id, value); ctx.stack.add_var(*var_id, value);
Ok(Continue) Ok(Continue)
} }
Instruction::DropVariable { var_id } => {
ctx.stack.remove_var(*var_id);
Ok(Continue)
}
Instruction::LoadEnv { dst, key } => { Instruction::LoadEnv { dst, key } => {
let key = ctx.get_str(*key, *span)?; let key = ctx.get_str(*key, *span)?;
if let Some(value) = get_env_var_case_insensitive(ctx, key) { if let Some(value) = get_env_var_case_insensitive(ctx, key) {
@ -1332,9 +1345,19 @@ fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str
} }
/// Helper to collect values into [`PipelineData`], preserving original span and metadata /// Helper to collect values into [`PipelineData`], preserving original span and metadata
///
/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> { fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, ShellError> {
let span = data.span().unwrap_or(fallback_span); let span = data.span().unwrap_or(fallback_span);
let metadata = data.metadata(); let metadata = match data.metadata() {
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
// check where some input came from.
Some(PipelineMetadata {
data_source: DataSource::FilePath(_),
content_type: None,
}) => None,
other => other,
};
let value = data.into_value(span)?; let value = data.into_value(span)?;
Ok(PipelineData::Value(value, metadata)) Ok(PipelineData::Value(value, metadata))
} }

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod call_ext; mod call_ext;
mod closure_eval; mod closure_eval;
pub mod column; pub mod column;

View File

@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-explore" name = "nu-explore"
version = "0.95.1" version = "0.96.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.95.1" } nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" } nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-table = { path = "../nu-table", version = "0.95.1" } nu-table = { path = "../nu-table", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.95.1" } nu-json = { path = "../nu-json", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.96.2" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.2" }
anyhow = { workspace = true } anyhow = { workspace = true }
log = { workspace = true } log = { workspace = true }

View File

@ -0,0 +1,5 @@
Implementation of the interactive `explore` command pager.
## Internal Nushell crate
This crate implements components of Nushell and is not designed to support plugin authors or other users directly.

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
mod commands; mod commands;
mod default_context; mod default_context;
mod explore; mod explore;

Some files were not shown because too many files have changed in this diff Show More