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
# Create a release only in nushell/nightly repo
- 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') }}
with:
prerelease: true

View File

@ -161,8 +161,12 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
let releaseStem = $'($bin)-($version)-($target)'
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://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
if (get-env _EXTRA_) == 'msi' {

View File

@ -91,7 +91,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release
- 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/') }}
with:
draft: true

View File

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

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.77.2"
version = "0.95.1"
rust-version = "1.78.0"
version = "0.96.2"
# 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"
dialoguer = { default-features = false, version = "0.11" }
digest = { default-features = false, version = "0.10" }
dirs-next = "2.0"
dirs = "5.0"
dirs-sys = "0.4"
dtparse = "2.0"
encoding_rs = "0.8"
fancy-regex = "0.13"
@ -115,12 +116,12 @@ mockito = { version = "1.4", default-features = false }
native-tls = "0.2"
nix = { version = "0.28", 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-traits = "0.2"
omnipath = "0.1"
once_cell = "1.18"
open = "5.2"
open = "5.3"
os_pipe = { version = "1.2", features = ["io_safety"] }
pathdiff = "0.2"
percent-encoding = "2"
@ -137,7 +138,7 @@ quote = "1.0"
rand = "0.8"
ratatui = "0.26"
rayon = "1.10"
reedline = "0.32.0"
reedline = "0.33.0"
regex = "1.9.5"
rmp = "0.8"
rmp-serde = "1.3"
@ -145,7 +146,7 @@ ropey = "1.6.1"
roxmltree = "0.19"
rstest = { version = "0.18", default-features = false }
rusqlite = "0.31"
rust-embed = "8.4.0"
rust-embed = "8.5.0"
same-file = "1.0"
serde = { version = "1.0", default-features = false }
serde_json = "1.0"
@ -173,35 +174,36 @@ uu_mv = "0.0.27"
uu_whoami = "0.0.27"
uu_uname = "0.0.27"
uucore = "0.0.27"
uuid = "1.9.1"
uuid = "1.10.0"
v_htmlescape = "0.15.0"
wax = "0.6"
which = "6.0.0"
windows = "0.54"
windows-sys = "0.48"
winreg = "0.52"
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.95.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" }
nu-command = { path = "./crates/nu-command", version = "0.95.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.95.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.95.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.95.1" }
nu-path = { path = "./crates/nu-path", version = "0.95.1" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
nu-std = { path = "./crates/nu-std", version = "0.95.1" }
nu-system = { path = "./crates/nu-system", version = "0.95.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
nu-cli = { path = "./crates/nu-cli", version = "0.96.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.2" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.2", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.2" }
nu-command = { path = "./crates/nu-command", version = "0.96.2" }
nu-engine = { path = "./crates/nu-engine", version = "0.96.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.96.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.96.2" }
nu-path = { path = "./crates/nu-path", version = "0.96.2" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.96.2" }
nu-std = { path = "./crates/nu-std", version = "0.96.2" }
nu-system = { path = "./crates/nu-system", version = "0.96.2" }
nu-utils = { path = "./crates/nu-utils", version = "0.96.2" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true }
ctrlc = { workspace = true }
dirs-next = { workspace = true }
dirs = { workspace = true }
log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.42", default-features = false, optional = true }
@ -225,11 +227,11 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.96.2" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.2" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.2" }
assert_cmd = "2.0"
dirs-next = { workspace = true }
dirs = { workspace = true }
tango-bench = "0.5"
pretty_assertions = { 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
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# [patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
# 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"
license = "MIT"
name = "nu-cli"
version = "0.95.1"
version = "0.96.2"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-command = { path = "../nu-command", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-command = { path = "../nu-command", version = "0.96.2" }
nu-test-support = { path = "../nu-test-support", version = "0.96.2" }
rstest = { workspace = true, default-features = false }
tempfile = { workspace = true }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-ansi-term = { workspace = true }
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
let item_id_value = Value::int(
match entry.id {
Some(id) => {
let ids = id.to_string();
match ids.parse::<i64>() {
Ok(i) => i,
_ => 0i64,
}
}
None => 0i64,
},
entry
.id
.and_then(|id| id.to_string().parse::<i64>().ok())
.unwrap_or_default(),
head,
);
let start_timestamp_value = Value::string(
match entry.start_timestamp {
Some(time) => time.to_string(),
None => "".into(),
},
entry
.start_timestamp
.map(|time| time.to_string())
.unwrap_or_default(),
head,
);
let command_value = Value::string(entry.command_line, head);
let session_id_value = Value::int(
match entry.session_id {
Some(sid) => {
let sids = sid.to_string();
match sids.parse::<i64>() {
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(),
},
entry
.session_id
.and_then(|id| id.to_string().parse::<i64>().ok())
.unwrap_or_default(),
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(
match entry.duration {
Some(d) => d.as_nanos().try_into().unwrap_or(0),
None => 0,
},
entry
.duration
.and_then(|d| d.as_nanos().try_into().ok())
.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))
.collect::<Result<Vec<_>, ShellError>>()?;
let no_option_specified = presence.iter().all(|present| !*present);
let records = all_options
.iter()
.zip(presence)
.filter(|(_, present)| *present)
.filter(|(_, present)| no_option_specified || *present)
.flat_map(|(option, _)| get_records(option, call.head))
.collect();

View File

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

View File

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

View File

@ -10,9 +10,7 @@ use nu_protocol::{
levenshtein_distance, Span,
};
use nu_utils::get_ls_colors;
use std::path::{
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
};
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
use super::SortBy;
@ -93,16 +91,16 @@ enum OriginalCwd {
}
impl OriginalCwd {
fn apply(&self, mut p: PathBuiltFromString) -> String {
fn apply(&self, mut p: PathBuiltFromString, path_separator: char) -> String {
match self {
Self::None => {}
Self::Home => p.parts.insert(0, "~".to_string()),
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 {
ret.push(SEP);
ret.push(path_separator);
}
ret
}
@ -133,6 +131,14 @@ pub fn complete_item(
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
let partial = surround_remove(partial);
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 ls_colors = (engine_state.config.use_ls_colors_completions
&& engine_state.config.use_ansi_coloring)
@ -195,7 +201,7 @@ pub fn complete_item(
)
.into_iter()
.map(|p| {
let path = original_cwd.apply(p);
let path = original_cwd.apply(p, path_separator);
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(
&path,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,12 +76,21 @@ pub fn evaluate_file(
trace!("parsing file: {}", file_path_str);
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 let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err);
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.
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
if block.signature.name == "main" {

View File

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

View File

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

View File

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

View File

@ -321,16 +321,10 @@ mod test {
let env = engine_state.render_env_vars();
assert!(
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
);
assert!(
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!(matches!(env.get("FOO"), 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!(env.contains_key("PWD"));
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;

View File

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

View File

@ -186,7 +186,7 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
}
// 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 suggestions_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"
)
}
assert_eq!(
expected,
suggestions
.into_iter()
.map(|it| it.value)
.collect::<Vec<_>>()
);
let suggestoins_str = suggestions
.iter()
.map(|it| it.value.clone())
.collect::<Vec<_>>();
assert_eq!(expected, &suggestoins_str);
}
// append the separator to the converted path

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT"
name = "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
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
indexmap = { 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 hook;
pub mod input_handler;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.95.1"
version = "0.96.2"
# 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
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-json = { version = "0.95.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-json = { version = "0.96.2", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-pretty-hex = { version = "0.96.2", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.96.2" }
# Potential dependencies for extras
heck = { workspace = true }
@ -33,6 +33,6 @@ v_htmlescape = { workspace = true }
itertools = { workspace = true }
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-command = { path = "../nu-command", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-command = { path = "../nu-command", version = "0.96.2" }
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;
pub mod extra;
pub use extra::*;

View File

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

View File

@ -21,7 +21,9 @@ impl Command for Break {
fn extra_usage(&self) -> &str {
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 {

View File

@ -21,7 +21,9 @@ impl Command for Continue {
fn extra_usage(&self) -> &str {
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 {

View File

@ -4,7 +4,7 @@ use nu_protocol::{
ast::Block,
debugger::WithoutDebug,
engine::{StateDelta, StateWorkingSet},
Range,
report_error_new, Range,
};
use std::{
sync::Arc,
@ -124,7 +124,10 @@ pub fn eval_block(
nu_engine::eval_block::<WithoutDebug>(engine_state, &mut stack, &block, input)
.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(

View File

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

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT"
name = "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
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2" }
itertools = { workspace = true }

View File

@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.95.1"
version = "0.96.2"
[lib]
bench = false
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-json = { path = "../nu-json", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.96.2" }
nu-ansi-term = { workspace = true }
serde = { workspace = true, features = ["derive"] }
[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 matching_brackets_style;
mod nu_style;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "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
@ -13,21 +13,21 @@ version = "0.95.1"
bench = false
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-glob = { path = "../nu-glob", version = "0.95.1" }
nu-json = { path = "../nu-json", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.95.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-system = { path = "../nu-system", version = "0.95.1" }
nu-table = { path = "../nu-table", version = "0.95.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-glob = { path = "../nu-glob", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.96.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.2" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-system = { path = "../nu-system", version = "0.96.2" }
nu-table = { path = "../nu-table", version = "0.96.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.96.2" }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.95.1" }
nuon = { path = "../nuon", version = "0.96.2" }
alphanumeric-sort = { workspace = true }
base64 = { workspace = true }
@ -137,10 +137,10 @@ sqlite = ["rusqlite"]
trash-support = ["trash"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" }
nu-test-support = { path = "../nu-test-support", version = "0.96.2" }
dirs-next = { workspace = true }
dirs = { workspace = true }
mockito = { workspace = true, default-features = false }
quickcheck = { 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
};
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 {
arg
} else {
" ".to_string()
};
let character = character_arg.unwrap_or_else(|| " ".to_string());
let arg = Arguments {
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::Duration { val, .. } => Box::new(val),
Value::Date { val, .. } => Box::new(val),
Value::String { val, .. } => {
// don't store ansi escape sequences in the database
// escape single quotes
Box::new(nu_utils::strip_ansi_unlikely(&val).into_owned())
}
Value::String { val, .. } => Box::new(val),
Value::Binary { val, .. } => Box::new(val),
Value::Nothing { .. } => Box::new(rusqlite::types::Null),
val => {

View File

@ -42,32 +42,32 @@ impl Command for MetadataSet {
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
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 content_type: Option<String> = call.get_flag(engine_state, stack, "content-type")?;
let signals = engine_state.signals().clone();
let metadata = input
.metadata()
.clone()
.unwrap_or_default()
.with_content_type(content_type);
let mut metadata = match &mut input {
PipelineData::Value(_, metadata)
| PipelineData::ListStream(_, metadata)
| PipelineData::ByteStream(_, metadata) => metadata.take().unwrap_or_default(),
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) {
(Some(path), false) => Ok(input.into_pipeline_data_with_metadata(
head,
signals,
metadata.with_data_source(DataSource::FilePath(path.into())),
)),
(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)),
(Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()),
(None, true) => metadata.data_source = DataSource::Ls,
(Some(_), true) => (), // TODO: error here
(None, false) => (),
}
Ok(input.set_metadata(Some(metadata)))
}
fn examples(&self) -> Vec<Example> {
@ -85,7 +85,9 @@ impl Command for MetadataSet {
Example {
description: "Set the metadata of a file path",
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_protocol::{debugger::Profiler, engine::Closure};
use nu_protocol::{
debugger::{Profiler, ProfilerOptions},
engine::Closure,
};
#[derive(Clone)]
pub struct DebugProfile;
@ -28,6 +31,7 @@ impl Command for DebugProfile {
Some('v'),
)
.switch("expr", "Collect expression types", Some('x'))
.switch("instructions", "Collect IR instructions", Some('i'))
.switch("lines", "Collect line numbers", Some('l'))
.named(
"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_values = call.has_flag(engine_state, stack, "values")?;
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 max_depth = call
.get_flag(engine_state, stack, "max-depth")?
.unwrap_or(2);
let profiler = Profiler::new(
max_depth,
collect_spans,
true,
collect_expanded_source,
collect_values,
collect_exprs,
collect_lines,
ProfilerOptions {
max_depth,
collect_spans,
collect_source: true,
collect_expanded_source,
collect_values,
collect_exprs,
collect_instructions,
collect_lines,
},
call.span(),
);

View File

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

View File

@ -31,7 +31,7 @@ mod test_examples {
check_example_evaluates_to_expected_output,
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::{
engine::{Command, EngineState, StateWorkingSet},
Type,
@ -81,6 +81,7 @@ mod test_examples {
working_set.add_decl(Box::new(Break));
working_set.add_decl(Box::new(Date));
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(Echo));
working_set.add_decl(Box::new(Enumerate));

View File

@ -121,9 +121,11 @@ impl Command for Save {
} else {
match stderr {
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)?;
}

View File

@ -30,6 +30,11 @@ impl Command for UMv {
example: "mv test.txt my/subdirectory",
result: None,
},
Example {
description: "Move only if source file is newer than target file",
example: "mv -u new/test.txt old/",
result: None,
},
Example {
description: "Move many files into a directory",
example: "mv *.txt my/subdirectory",
@ -49,6 +54,11 @@ impl Command for UMv {
.switch("verbose", "explain what is being done.", Some('v'))
.switch("progress", "display a progress bar", Some('p'))
.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'))
.rest(
"paths",
@ -77,6 +87,11 @@ impl Command for UMv {
} else {
uu_mv::OverwriteMode::Force
};
let update = if call.has_flag(engine_state, stack, "update")? {
UpdateMode::ReplaceIfOlder
} else {
UpdateMode::ReplaceAll
};
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
@ -164,7 +179,7 @@ impl Command for UMv {
verbose,
suffix: String::from("~"),
backup: BackupMode::NoBackup,
update: UpdateMode::ReplaceAll,
update,
target_dir: None,
no_target_dir: 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)",
Some('r'),
)
.switch("quiet", "Hide the initial status message (default: false)", Some('q'))
.switch("verbose", "Operate in verbose mode (default: false)", Some('v'))
.category(Category::FileSystem)
}
@ -94,6 +95,8 @@ impl Command for Watch {
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>> =
call.get_flag(engine_state, stack, "debounce-ms")?;
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.
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);

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:
"Get the env value of `MY_ENV` with a default value 'abc' if not present",
example: "$env | get --ignore-errors MY_ENV | default 'abc'",
result: None, // Some(Value::test_string("abc")),
result: Some(Value::test_string("abc")),
},
Example {
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(
vec![
Value::test_int(1),
@ -113,15 +113,7 @@ fn default(
} else if input.is_nothing() {
Ok(value.into_pipeline_data())
} else {
input
.map(
move |item| match item {
Value::Nothing { .. } => value.clone(),
x => x,
},
engine_state.signals(),
)
.map(|x| x.set_metadata(metadata))
Ok(input)
}
}

View File

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

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::ValueIterator;
use nu_protocol::{report_warning_new, ParseWarning, ValueIterator};
#[derive(Clone)]
pub struct Group;
@ -54,6 +54,17 @@ impl Command for Group {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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 metadata = input.metadata();

View File

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

View File

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

View File

@ -36,7 +36,7 @@ impl Command for Reduce {
}
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> {
@ -50,6 +50,11 @@ impl Command for Reduce {
description: "Sum values of a list (same as 'math sum')",
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:
"[ 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)",
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: 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'",
@ -110,8 +120,7 @@ impl Command for Reduce {
engine_state.signals().check(head)?;
acc = closure
.add_arg(value)
.add_arg(acc)
.run_with_input(PipelineData::Empty)?
.run_with_value(acc)?
.into_value(head)?;
}

View File

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

View File

@ -1,5 +1,6 @@
use nu_engine::command_prelude::*;
use nu_protocol::ValueIterator;
use nu_protocol::ListStream;
use std::num::NonZeroUsize;
#[derive(Clone)]
pub struct Window;
@ -12,8 +13,8 @@ impl Command for Window {
fn signature(&self) -> Signature {
Signature::build("window")
.input_output_types(vec![(
Type::List(Box::new(Type::Any)),
Type::List(Box::new(Type::List(Box::new(Type::Any)))),
Type::list(Type::Any),
Type::list(Type::list(Type::Any)),
)])
.required("window_size", SyntaxShape::Int, "The size of each window.")
.named(
@ -34,72 +35,41 @@ impl Command for Window {
"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> {
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![
Example {
example: "[1 2 3 4] | window 2",
description: "A sliding window of two elements",
result: Some(Value::list(
stream_test_1,
Span::test_data(),
)),
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(2), Value::test_int(3)]),
Value::test_list(vec![Value::test_int(3), Value::test_int(4)]),
])),
},
Example {
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",
result: Some(Value::list(
stream_test_2,
Span::test_data(),
)),
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(4), Value::test_int(5)]),
Value::test_list(vec![Value::test_int(7), Value::test_int(8)]),
])),
},
Example {
example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
description: "A sliding window of equal stride that includes remainder. Equivalent to chunking",
result: Some(Value::list(
stream_test_3,
Span::test_data(),
)),
result: Some(Value::test_list(vec![
Value::test_list(vec![
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,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let group_size: Spanned<usize> = call.req(engine_state, stack, 0)?;
let metadata = input.metadata();
let stride: Option<usize> = call.get_flag(engine_state, stack, "stride")?;
let window_size: Value = call.req(engine_state, stack, 0)?;
let stride: Option<Value> = call.get_flag(engine_state, stack, "stride")?;
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 {
group_size: group_size.item,
input: Box::new(input.into_iter()),
span: head,
previous: None,
stride,
remainder,
let stride = if let Some(stride_val) = stride {
let stride = usize::try_from(stride_val.as_int()?).map_err(|_| {
ShellError::NeedsPositiveValue {
span: stride_val.span(),
}
})?;
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(
head,
engine_state.signals().clone(),
metadata,
))
if remainder && size == stride {
super::chunks::chunks(engine_state, input, size, head)
} else if stride >= size {
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 {
group_size: usize,
input: ValueIterator,
span: Span,
previous: Option<Vec<Value>>,
struct WindowOverlapIter<I: Iterator<Item = Value>> {
iter: I,
window: Vec<Value>,
stride: usize,
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;
fn next(&mut self) -> Option<Self::Item> {
let mut group = self.previous.take().unwrap_or_else(|| {
let mut vec = Vec::new();
// 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;
}
}
}
}
let len = if self.window.is_empty() {
self.window.capacity()
} else {
// our historic buffer is already full, so stride instead
self.stride
};
loop {
let item = self.input.next();
self.window.extend((&mut self.iter).take(len));
match item {
Some(v) => {
group.push(v);
current_count += 1;
if current_count >= self.stride {
break;
}
}
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 self.window.len() == self.window.capacity()
|| (self.remainder && !self.window.is_empty())
{
let mut next = Vec::with_capacity(self.window.len());
next.extend(self.window.iter().skip(self.stride).cloned());
let window = std::mem::replace(&mut self.window, next);
Some(Value::list(window, self.span))
} else {
None
}
}
}
if group.is_empty() {
return None;
struct WindowGapIter<I: Iterator<Item = Value>> {
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();
self.previous = Some(group);
impl<I: Iterator<Item = Value>> Iterator for WindowGapIter<I> {
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 {
Signature::build("generate")
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
.required("initial", SyntaxShape::Any, "Initial value.")
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"Generator function.",
)
.optional("initial", SyntaxShape::Any, "Initial value.")
.allow_variants_without_examples(true)
.category(Category::Generators)
}
@ -41,7 +41,7 @@ used as the next argument to the closure, otherwise generation stops.
fn examples(&self) -> Vec<Example> {
vec![
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",
result: Some(Value::list(
vec![
@ -57,10 +57,17 @@ used as the next argument to the closure, otherwise generation stops.
},
Example {
example:
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
"generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } [0, 1]",
description: "Generate a continuous stream of Fibonacci numbers",
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,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let initial: Value = call.req(engine_state, stack, 0)?;
let closure: Closure = call.req(engine_state, stack, 1)?;
let closure: Closure = call.req(engine_state, stack, 0)?;
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);
// A type of Option<S> is used to represent state. Invocation
// will stop on None. Using Option<S> allows functions to output
// 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 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)]
mod test {
use super::*;

View File

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

View File

@ -29,6 +29,10 @@ impl Command for SubCommand {
vec!["square", "root"]
}
fn is_const(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
@ -44,6 +48,23 @@ impl Command for SubCommand {
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> {
vec![Example {
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) {
(Some(user), Some(password)) => {
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(user), _) => {
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(password)) => {
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)
}
_ => None,

View File

@ -15,7 +15,11 @@ impl Command for SubCommand {
Signature::build("random int")
.input_output_types(vec![(Type::Nothing, Type::Int)])
.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)
}
@ -40,12 +44,12 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Generate an unconstrained random integer",
description: "Generate a non-negative random integer",
example: "random int",
result: None,
},
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",
result: None,
},
@ -55,8 +59,8 @@ impl Command for SubCommand {
result: None,
},
Example {
description: "Generate a random integer between 1 and 10",
example: "random int 1..10",
description: "Generate a random integer between -10 (inclusive) and 10 (inclusive)",
example: "random int (-10)..10",
result: None,
},
]

View File

@ -3,6 +3,7 @@ use nu_engine::command_prelude::*;
use nu_protocol::Signals;
use once_cell::sync::Lazy;
use std::collections::HashSet;
// Character used to separate directories in a Path Environment variable on windows is ";"
#[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 {
fn name(&self) -> &str {
"char"
@ -297,6 +316,11 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
CHAR_MAP
.iter()
.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(
s.chars()
.map(|c| format!("{:x}", c as u32))
@ -306,7 +330,7 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData {
);
let record = record! {
"name" => Value::string(*name, call_span),
"character" => Value::string(s, call_span),
"character" => character,
"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
// [^\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 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();
match v {
@ -422,4 +422,9 @@ mod test {
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);
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()];
assert_eq!(actual, expected);
})

View File

@ -15,12 +15,3 @@ fn break_while_loop() {
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]
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");
}
#[test]
fn err_pipe_redirection() {
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");
}

View File

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

View File

@ -23,6 +23,13 @@ fn let_takes_pipeline() {
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]
fn let_pipeline_allows_in() {
let actual =
@ -38,6 +45,13 @@ fn mut_takes_pipeline() {
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]
fn mut_pipeline_allows_in() {
let actual =

View File

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

View File

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

View File

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

View File

@ -463,3 +463,65 @@ fn save_same_file_with_collect_and_filter() {
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#"
{sample}
| select nu."0xATYKARNU" nu.committers.name nu.releases.version
| get nu_releases_version
| get "nu.releases.version"
| where $it > "0.8"
| 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();
}
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]
@ -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");
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"
name = "nu-derive-value"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
version = "0.95.1"
version = "0.96.2"
[lib]
proc-macro = true

View File

@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
edition = "2021"
license = "MIT"
name = "nu-engine"
version = "0.95.1"
version = "0.96.2"
[lib]
bench = false
[dependencies]
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.95.1" }
nu-glob = { path = "../nu-glob", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.2" }
nu-path = { path = "../nu-path", version = "0.96.2" }
nu-glob = { path = "../nu-glob", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.96.2" }
log = { workspace = true }
[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::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
Instruction::DropVariable { var_id: _ } => Ok(()),
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
@ -422,7 +423,7 @@ impl BlockBuilder {
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 {
self.instructions.len()
}

View File

@ -171,6 +171,27 @@ pub(crate) fn compile_expression(
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) => {
let block = working_set.get_block(*block_id);
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
@ -423,7 +444,15 @@ pub(crate) fn compile_expression(
working_set,
builder,
&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,
out_reg,
)?;

View File

@ -10,8 +10,8 @@ use nu_protocol::{
debugger::DebugContext,
engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
eval_base::Eval,
ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span,
Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
ByteStreamSource, Config, DataSource, FromValue, IntoPipelineData, OutDest, PipelineData,
PipelineMetadata, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
};
use nu_utils::IgnoreCaseExt;
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
caller_stack.config = callee_stack.config.clone();
caller_stack.config.clone_from(&callee_stack.config);
}
fn eval_external(
@ -259,6 +259,10 @@ pub fn eval_expression_with_input<D: DebugContext>(
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) => {
let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c
@ -605,6 +609,44 @@ pub fn eval_block<D: DebugContext>(
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>(
engine_state: &EngineState,
stack: &mut Stack,
@ -729,6 +771,18 @@ impl Eval for EvalRuntime {
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>(
engine_state: &EngineState,
stack: &mut Stack,

View File

@ -6,9 +6,9 @@ use nu_protocol::{
debugger::DebugContext,
engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack},
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream,
OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature,
Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
record, ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned,
ListStream, OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId,
ShellError, Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
};
use nu_utils::IgnoreCaseExt;
@ -184,11 +184,20 @@ fn eval_ir_block_impl<D: DebugContext>(
let instruction = &ir_block.instructions[pc];
let span = &ir_block.spans[pc];
let ast = &ir_block.ast[pc];
log::trace!(
"{pc:-4}: {}",
instruction.display(ctx.engine_state, ctx.data)
D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers);
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) => {
pc += 1;
}
@ -336,6 +345,10 @@ fn eval_instruction<D: DebugContext>(
ctx.stack.add_var(*var_id, value);
Ok(Continue)
}
Instruction::DropVariable { var_id } => {
ctx.stack.remove_var(*var_id);
Ok(Continue)
}
Instruction::LoadEnv { dst, key } => {
let key = ctx.get_str(*key, *span)?;
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
///
/// 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> {
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)?;
Ok(PipelineData::Value(value, metadata))
}

View File

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

View File

@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
edition = "2021"
license = "MIT"
name = "nu-explore"
version = "0.95.1"
version = "0.96.2"
[lib]
bench = false
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-table = { path = "../nu-table", version = "0.95.1" }
nu-json = { path = "../nu-json", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.96.2" }
nu-parser = { path = "../nu-parser", version = "0.96.2" }
nu-color-config = { path = "../nu-color-config", version = "0.96.2" }
nu-engine = { path = "../nu-engine", version = "0.96.2" }
nu-table = { path = "../nu-table", version = "0.96.2" }
nu-json = { path = "../nu-json", version = "0.96.2" }
nu-utils = { path = "../nu-utils", version = "0.96.2" }
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 }
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 default_context;
mod explore;

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