mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
2a08a18b26 | |||
d5aad7a4ef | |||
5bc21fbb0a | |||
f136e0601d | |||
c00a05a762 | |||
51aa66fef7 | |||
25b90744b7 | |||
e810995cf8 | |||
f0a073b397 | |||
cd00a489af | |||
cbf7feef22 | |||
a6afc89338 | |||
c7b9183e47 | |||
b6c537d782 |
12
.github/workflows/check-msrv.nu
vendored
Normal file
12
.github/workflows/check-msrv.nu
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
let toolchain_spec = open rust-toolchain.toml | get toolchain.channel
|
||||
let msrv_spec = open Cargo.toml | get package.rust-version
|
||||
|
||||
# This check is conservative in the sense that we use `rust-toolchain.toml`'s
|
||||
# override to ensure that this is the upper-bound for the minimum supported
|
||||
# rust version
|
||||
if $toolchain_spec != $msrv_spec {
|
||||
print -e "Mismatching rust compiler versions specified in `Cargo.toml` and `rust-toolchain.toml`"
|
||||
print -e $"Cargo.toml: ($msrv_spec)"
|
||||
print -e $"rust-toolchain.toml: ($toolchain_spec)"
|
||||
exit 1
|
||||
}
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -130,6 +130,9 @@ jobs:
|
||||
- name: Standard library tests
|
||||
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
||||
|
||||
- name: Ensure that Cargo.toml MSRV and rust-toolchain.toml use the same version
|
||||
run: nu .github/workflows/check-msrv.nu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
||||
name: Std
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- aarch64-apple-darwin
|
||||
@ -78,10 +79,11 @@ jobs:
|
||||
run: |
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
@ -167,10 +169,11 @@ jobs:
|
||||
run: |
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.19.0
|
||||
uses: crate-ci/typos@v1.20.3
|
||||
|
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -2671,7 +2671,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm",
|
||||
@ -2722,7 +2722,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
@ -2756,7 +2756,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
@ -2768,7 +2768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-dataframe"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
@ -2791,7 +2791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck 0.5.0",
|
||||
@ -2816,7 +2816,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"itertools 0.12.0",
|
||||
"nu-engine",
|
||||
@ -2828,7 +2828,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
@ -2840,7 +2840,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.0",
|
||||
@ -2944,7 +2944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
@ -2954,7 +2954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"crossterm",
|
||||
@ -2976,14 +2976,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"num-traits",
|
||||
@ -2993,7 +2993,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
@ -3014,7 +3014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
@ -3030,7 +3030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"omnipath",
|
||||
@ -3039,7 +3039,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"log",
|
||||
@ -3057,7 +3057,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
@ -3071,7 +3071,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
@ -3080,7 +3080,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"byte-unit",
|
||||
"chrono",
|
||||
@ -3105,7 +3105,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
@ -3116,7 +3116,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"libc",
|
||||
@ -3133,7 +3133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
@ -3147,7 +3147,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
@ -3155,7 +3155,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
@ -3167,7 +3167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
@ -3191,7 +3191,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
@ -3201,7 +3201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"eml-parser",
|
||||
"ical",
|
||||
@ -3214,7 +3214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
@ -3223,7 +3223,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
@ -3232,7 +3232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
|
38
Cargo.toml
38
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.74.1"
|
||||
version = "0.92.0"
|
||||
rust-version = "1.77.2"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -162,24 +162,24 @@ windows = "0.54"
|
||||
winreg = "0.52"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.92.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.92.0" }
|
||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.92.0", features = [
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.92.2" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.92.2" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.92.2" }
|
||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.92.2", features = [
|
||||
"dataframe",
|
||||
], optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.92.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.92.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.92.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.92.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.92.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.92.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.92.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.92.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.92.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.92.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.92.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.92.0" }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.92.2" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.92.2" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.92.2" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.92.2" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.92.2" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.92.2" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.92.2" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.92.2" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.92.2" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.92.2" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.92.2" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.92.2" }
|
||||
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
@ -208,7 +208,7 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.92.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.92.2" }
|
||||
assert_cmd = "2.0"
|
||||
dirs-next = { workspace = true }
|
||||
divan = "0.1.14"
|
||||
|
@ -5,25 +5,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -132,7 +132,7 @@ impl Command for Commandline {
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "`--replace (-r)` is deprecated".into(),
|
||||
msg: "Replaceing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(),
|
||||
msg: "Replacing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(),
|
||||
span: Some(call.arguments_span()),
|
||||
help: Some("Use `commandline edit --replace (-r)`".into()),
|
||||
inner: vec![],
|
||||
|
@ -267,7 +267,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
//Reset the ctrl-c handler
|
||||
// Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
@ -281,10 +281,42 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let env_change = engine_state.get_config().hooks.env_change.clone();
|
||||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
let engine_reference = Arc::new(engine_state.clone());
|
||||
let config = engine_state.get_config();
|
||||
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Find the configured cursor shapes for each mode
|
||||
let cursor_config = CursorConfig {
|
||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
||||
@ -304,7 +336,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// at this line we have cloned the state for the completer and the transient prompt
|
||||
// until we drop those, we cannot use the stack in the REPL loop itself
|
||||
// See STACK-REFERENCE to see where we have taken a reference
|
||||
let mut stack_arc = Arc::new(stack);
|
||||
let stack_arc = Arc::new(stack);
|
||||
|
||||
let mut line_editor = line_editor
|
||||
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
||||
@ -427,50 +459,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(
|
||||
engine_state,
|
||||
&mut Stack::with_parent(stack_arc.clone()),
|
||||
None,
|
||||
vec![],
|
||||
&hook,
|
||||
"pre_prompt",
|
||||
) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) = hook::eval_env_change_hook(
|
||||
config.hooks.env_change.clone(),
|
||||
engine_state,
|
||||
&mut Stack::with_parent(stack_arc.clone()),
|
||||
) {
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = &engine_state.get_config().clone();
|
||||
prompt_update::update_prompt(
|
||||
@ -508,6 +496,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
.with_completer(Box::<DefaultCompleter>::default());
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
let mut stack = Stack::unwrap_unique(stack_arc);
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let hostname = System::host_name();
|
||||
@ -530,7 +520,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
|
||||
if let Err(err) = eval_hook(
|
||||
engine_state,
|
||||
&mut Stack::with_parent(stack_arc.clone()),
|
||||
&mut stack,
|
||||
None,
|
||||
vec![],
|
||||
&hook,
|
||||
@ -552,8 +542,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// Actual command execution logic starts from here
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut stack = Stack::unwrap_unique(stack_arc);
|
||||
|
||||
match parse_operation(s.clone(), engine_state, &stack) {
|
||||
Ok(operation) => match operation {
|
||||
ReplOperation::AutoCd { cwd, target, span } => {
|
||||
@ -598,22 +586,20 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
}
|
||||
|
||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||
// put the stack back into the arc
|
||||
stack_arc = Arc::new(stack);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
}
|
||||
println!();
|
||||
return (false, Stack::unwrap_unique(stack_arc), line_editor);
|
||||
return (false, stack, line_editor);
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
@ -625,7 +611,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack_arc, engine_state));
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -647,7 +633,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
use_color,
|
||||
);
|
||||
|
||||
(true, Stack::unwrap_unique(stack_arc), line_editor)
|
||||
(true, stack, line_editor)
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -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.92.0"
|
||||
version = "0.92.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.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-dataframe"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,9 +13,9 @@ version = "0.92.0"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false }
|
||||
@ -72,4 +72,4 @@ dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
|
@ -22,7 +22,7 @@ impl Command for LazyJoin {
|
||||
.required("right_on", SyntaxShape::Any, "Right column(s) to join on")
|
||||
.switch(
|
||||
"inner",
|
||||
"inner joing between lazyframes (default)",
|
||||
"inner join between lazyframes (default)",
|
||||
Some('i'),
|
||||
)
|
||||
.switch("left", "left join between lazyframes", Some('l'))
|
||||
|
@ -34,6 +34,10 @@ impl CustomValue for NuDataFrame {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn follow_path_int(
|
||||
&self,
|
||||
_self_span: Span,
|
||||
|
@ -34,6 +34,10 @@ impl CustomValue for NuExpression {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn operation(
|
||||
&self,
|
||||
lhs_span: Span,
|
||||
|
@ -43,4 +43,8 @@ impl CustomValue for NuLazyFrame {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -37,4 +37,8 @@ impl CustomValue for NuLazyGroupBy {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -34,4 +34,8 @@ impl CustomValue for NuWhen {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -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.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,13 +13,13 @@ version = "0.92.0"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-json = { version = "0.92.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-pretty-hex = { version = "0.92.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-json = { version = "0.92.2", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-pretty-hex = { version = "0.92.2", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
@ -37,6 +37,6 @@ extra = ["default"]
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
|
@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.26", default-features = false }
|
||||
|
@ -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.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,19 +13,19 @@ version = "0.92.0"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.92.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.92.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-system = { path = "../nu-system", version = "0.92.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.92.2" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
@ -134,8 +134,8 @@ trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
|
||||
dirs-next = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -379,6 +379,10 @@ impl CustomValue for SQLiteDatabase {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn follow_path_int(
|
||||
&self,
|
||||
_self_span: Span,
|
||||
|
@ -1,8 +1,12 @@
|
||||
use filetime::FileTime;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
|
||||
use std::{fs::OpenOptions, path::Path, time::SystemTime};
|
||||
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Touch;
|
||||
|
||||
@ -18,7 +22,11 @@ impl Command for Touch {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("touch")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.rest("files", SyntaxShape::Filepath, "The file(s) to create.")
|
||||
.rest(
|
||||
"files",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Filepath]),
|
||||
"The file(s) to create."
|
||||
)
|
||||
.named(
|
||||
"reference",
|
||||
SyntaxShape::String,
|
||||
@ -58,7 +66,7 @@ impl Command for Touch {
|
||||
let mut change_atime: bool = call.has_flag(engine_state, stack, "access")?;
|
||||
let reference: Option<Spanned<String>> = call.get_flag(engine_state, stack, "reference")?;
|
||||
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
|
||||
let files: Vec<String> = call.rest(engine_state, stack, 0)?;
|
||||
let files: Vec<Spanned<NuGlob>> = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
||||
if files.is_empty() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
@ -105,8 +113,10 @@ impl Command for Touch {
|
||||
})?;
|
||||
}
|
||||
|
||||
for (index, item) in files.into_iter().enumerate() {
|
||||
let path = Path::new(&item);
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
for (index, glob) in files.into_iter().enumerate() {
|
||||
let path = expand_path_with(glob.item.as_ref(), &cwd, glob.item.is_expand());
|
||||
|
||||
// If --no-create is passed and the file/dir does not exist there's nothing to do
|
||||
if no_create && !path.exists() {
|
||||
@ -119,7 +129,7 @@ impl Command for Touch {
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(path)
|
||||
.open(&path)
|
||||
{
|
||||
return Err(ShellError::CreateNotPossible {
|
||||
msg: format!("Failed to create file: {err}"),
|
||||
@ -132,7 +142,7 @@ impl Command for Touch {
|
||||
}
|
||||
|
||||
if change_mtime {
|
||||
if let Err(err) = filetime::set_file_mtime(&item, FileTime::from_system_time(mtime))
|
||||
if let Err(err) = filetime::set_file_mtime(&path, FileTime::from_system_time(mtime))
|
||||
{
|
||||
return Err(ShellError::ChangeModifiedTimeNotPossible {
|
||||
msg: format!("Failed to change the modified time: {err}"),
|
||||
@ -145,7 +155,7 @@ impl Command for Touch {
|
||||
}
|
||||
|
||||
if change_atime {
|
||||
if let Err(err) = filetime::set_file_atime(&item, FileTime::from_system_time(atime))
|
||||
if let Err(err) = filetime::set_file_atime(&path, FileTime::from_system_time(atime))
|
||||
{
|
||||
return Err(ShellError::ChangeAccessTimeNotPossible {
|
||||
msg: format!("Failed to change the access time: {err}"),
|
||||
|
@ -1,10 +1,11 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use uu_mkdir::mkdir;
|
||||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UMkdir;
|
||||
|
||||
@ -39,7 +40,7 @@ impl Command for UMkdir {
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::Directory,
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Directory]),
|
||||
"The name(s) of the path(s) to create.",
|
||||
)
|
||||
.switch(
|
||||
@ -57,10 +58,10 @@ impl Command for UMkdir {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut directories = call
|
||||
.rest::<String>(engine_state, stack, 0)?
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut directories = get_rest_for_glob_pattern(engine_state, stack, call, 0)?
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(|dir| nu_path::expand_path_with(dir.item.as_ref(), &cwd, dir.item.is_expand()))
|
||||
.peekable();
|
||||
|
||||
let is_verbose = call.has_flag(engine_state, stack, "verbose")?;
|
||||
|
@ -128,7 +128,7 @@ impl Command for InputList {
|
||||
// ..Default::default()
|
||||
// };
|
||||
|
||||
let ans: InteractMode = if multi {
|
||||
let answer: InteractMode = if multi {
|
||||
let multi_select = MultiSelect::new(); //::with_theme(&theme);
|
||||
|
||||
InteractMode::Multi(
|
||||
@ -179,7 +179,7 @@ impl Command for InputList {
|
||||
)
|
||||
};
|
||||
|
||||
Ok(match ans {
|
||||
Ok(match answer {
|
||||
InteractMode::Multi(res) => {
|
||||
if index {
|
||||
match res {
|
||||
|
@ -502,3 +502,16 @@ fn create_a_file_with_tilde() {
|
||||
assert!(files_exist_at(vec![Path::new("~tilde2")], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respects_cwd() {
|
||||
Playground::setup("touch_respects_cwd", |dirs, _sandbox| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"mkdir 'dir'; cd 'dir'; touch 'i_will_be_created.txt'"
|
||||
);
|
||||
|
||||
let path = dirs.test().join("dir/i_will_be_created.txt");
|
||||
assert!(path.exists());
|
||||
})
|
||||
}
|
||||
|
@ -123,6 +123,20 @@ fn creates_directory_three_dots_quotation_marks() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respects_cwd() {
|
||||
Playground::setup("mkdir_respects_cwd", |dirs, _| {
|
||||
nu!(
|
||||
cwd: dirs.test(),
|
||||
"mkdir 'some_folder'; cd 'some_folder'; mkdir 'another/deeper_one'"
|
||||
);
|
||||
|
||||
let expected = dirs.test().join("some_folder/another/deeper_one");
|
||||
|
||||
assert!(expected.exists());
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn mkdir_umask_permission() {
|
||||
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-engine"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-explore"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.92.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.92.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.92.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.92.2" }
|
||||
|
||||
terminal_size = { workspace = true }
|
||||
strip-ansi-escapes = { workspace = true }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nu-glob"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
description = """
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -23,5 +23,5 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-path = { path="../nu-path", version = "0.92.0" }
|
||||
# nu-path = { path="../nu-path", version = "0.92.2" }
|
||||
# serde_json = "1.0"
|
||||
|
@ -66,7 +66,7 @@ fn main() {
|
||||
println!("first: {}", array.first().unwrap());
|
||||
|
||||
// Add a value
|
||||
array.push(Value::String("tak".to_string()));
|
||||
array.push(Value::String("baz".to_string()));
|
||||
}
|
||||
|
||||
// Encode to Hjson
|
||||
@ -76,4 +76,4 @@ fn main() {
|
||||
```
|
||||
# DOCS
|
||||
|
||||
At the moment, the documentation on [serde_hjson](https://docs.rs/serde-hjson/0.9.1/serde_hjson/) / [serde_json](https://docs.rs/serde_json/1.0.93/serde_json/) is also relevant for nu-json.
|
||||
At the moment, the documentation on [serde_hjson](https://docs.rs/serde-hjson/0.9.1/serde_hjson/) / [serde_json](https://docs.rs/serde_json/1.0.93/serde_json/) is also relevant for nu-json.
|
||||
|
@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
|
||||
description = "Nushell's integrated LSP server"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
||||
name = "nu-lsp"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "../nu-cli", version = "0.92.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-cli = { path = "../nu-cli", version = "0.92.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
reedline = { workspace = true }
|
||||
|
||||
@ -23,8 +23,8 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.92.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
|
||||
assert-json-diff = "2.0"
|
||||
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-parser"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
bytesize = { workspace = true }
|
||||
chrono = { default-features = false, features = ['std'], workspace = true }
|
||||
|
@ -2323,6 +2323,11 @@ pub fn parse_unit_value<'res>(
|
||||
let lhs = strip_underscores(value[..lhs_len].as_bytes());
|
||||
let lhs_span = Span::new(span.start, span.start + lhs_len);
|
||||
let unit_span = Span::new(span.start + lhs_len, span.end);
|
||||
if lhs.ends_with('$') {
|
||||
// If `parse_unit_value` has higher precedence over `parse_range`,
|
||||
// a variable with the name of a unit could otherwise not be used as the end of a range.
|
||||
return None;
|
||||
}
|
||||
|
||||
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
||||
Ok(it) => it,
|
||||
|
@ -1191,6 +1191,16 @@ mod range {
|
||||
|
||||
assert!(!working_set.parse_errors.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vars_not_read_as_units() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let _ = parse(&mut working_set, None, b"0..<$day", true);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-path"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
|
@ -1,17 +1,18 @@
|
||||
[package]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Testing support for Nushell plugins"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-test-support"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.0", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.92.2", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
similar = "2.4"
|
||||
|
||||
|
@ -136,7 +136,7 @@ impl PluginTest {
|
||||
move |mut value| match PluginCustomValue::serialize_custom_values_in(&mut value) {
|
||||
Ok(()) => {
|
||||
// Make sure to mark them with the source so they pass correctly, too.
|
||||
PluginCustomValue::add_source(&mut value, &source);
|
||||
let _ = PluginCustomValue::add_source_in(&mut value, &source);
|
||||
value
|
||||
}
|
||||
Err(err) => Value::error(err, value.span()),
|
||||
|
@ -35,6 +35,10 @@ impl CustomValue for CustomU32 {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
|
||||
other
|
||||
.as_custom_value()
|
||||
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
bincode = "1.3"
|
||||
rmp-serde = "1.1"
|
||||
|
@ -67,7 +67,6 @@ mod plugin;
|
||||
mod protocol;
|
||||
mod sequence;
|
||||
mod serializers;
|
||||
mod util;
|
||||
|
||||
pub use plugin::{
|
||||
serve_plugin, EngineInterface, Plugin, PluginCommand, PluginEncoder, PluginRead, PluginWrite,
|
||||
@ -88,6 +87,8 @@ pub use plugin::{
|
||||
pub use protocol::{PluginCustomValue, PluginInput, PluginOutput};
|
||||
#[doc(hidden)]
|
||||
pub use serializers::EncodingType;
|
||||
#[doc(hidden)]
|
||||
pub mod util;
|
||||
|
||||
// Used by external benchmarks.
|
||||
#[doc(hidden)]
|
||||
|
@ -231,6 +231,9 @@ pub trait Interface: Clone + Send {
|
||||
/// The output message type, which must be capable of encapsulating a [`StreamMessage`].
|
||||
type Output: From<StreamMessage>;
|
||||
|
||||
/// Any context required to construct [`PipelineData`]. Can be `()` if not needed.
|
||||
type DataContext;
|
||||
|
||||
/// Write an output message.
|
||||
fn write(&self, output: Self::Output) -> Result<(), ShellError>;
|
||||
|
||||
@ -245,7 +248,11 @@ pub trait Interface: Clone + Send {
|
||||
|
||||
/// Prepare [`PipelineData`] to be written. This is called by `init_write_pipeline_data()` as
|
||||
/// a hook so that values that need special handling can be taken care of.
|
||||
fn prepare_pipeline_data(&self, data: PipelineData) -> Result<PipelineData, ShellError>;
|
||||
fn prepare_pipeline_data(
|
||||
&self,
|
||||
data: PipelineData,
|
||||
context: &Self::DataContext,
|
||||
) -> Result<PipelineData, ShellError>;
|
||||
|
||||
/// Initialize a write for [`PipelineData`]. This returns two parts: the header, which can be
|
||||
/// embedded in the particular message that references the stream, and a writer, which will
|
||||
@ -258,6 +265,7 @@ pub trait Interface: Clone + Send {
|
||||
fn init_write_pipeline_data(
|
||||
&self,
|
||||
data: PipelineData,
|
||||
context: &Self::DataContext,
|
||||
) -> Result<(PipelineDataHeader, PipelineDataWriter<Self>), ShellError> {
|
||||
// Allocate a stream id and a writer
|
||||
let new_stream = |high_pressure_mark: i32| {
|
||||
@ -269,7 +277,7 @@ pub trait Interface: Clone + Send {
|
||||
.write_stream(id, self.clone(), high_pressure_mark)?;
|
||||
Ok::<_, ShellError>((id, writer))
|
||||
};
|
||||
match self.prepare_pipeline_data(data)? {
|
||||
match self.prepare_pipeline_data(data, context)? {
|
||||
PipelineData::Value(value, _) => {
|
||||
Ok((PipelineDataHeader::Value(value), PipelineDataWriter::None))
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ impl EngineInterface {
|
||||
) -> Result<PipelineDataWriter<Self>, ShellError> {
|
||||
match result {
|
||||
Ok(data) => {
|
||||
let (header, writer) = match self.init_write_pipeline_data(data) {
|
||||
let (header, writer) = match self.init_write_pipeline_data(data, &()) {
|
||||
Ok(tup) => tup,
|
||||
// If we get an error while trying to construct the pipeline data, send that
|
||||
// instead
|
||||
@ -438,7 +438,7 @@ impl EngineInterface {
|
||||
let mut writer = None;
|
||||
|
||||
let call = call.map_data(|input| {
|
||||
let (input_header, input_writer) = self.init_write_pipeline_data(input)?;
|
||||
let (input_header, input_writer) = self.init_write_pipeline_data(input, &())?;
|
||||
writer = Some(input_writer);
|
||||
Ok(input_header)
|
||||
})?;
|
||||
@ -809,6 +809,7 @@ impl EngineInterface {
|
||||
|
||||
impl Interface for EngineInterface {
|
||||
type Output = PluginOutput;
|
||||
type DataContext = ();
|
||||
|
||||
fn write(&self, output: PluginOutput) -> Result<(), ShellError> {
|
||||
log::trace!("to engine: {:?}", output);
|
||||
@ -827,7 +828,11 @@ impl Interface for EngineInterface {
|
||||
&self.stream_manager_handle
|
||||
}
|
||||
|
||||
fn prepare_pipeline_data(&self, mut data: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
fn prepare_pipeline_data(
|
||||
&self,
|
||||
mut data: PipelineData,
|
||||
_context: &(),
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// Serialize custom values in the pipeline data
|
||||
match data {
|
||||
PipelineData::Value(ref mut value, _) => {
|
||||
|
@ -1085,10 +1085,13 @@ fn interface_eval_closure_with_stream() -> Result<(), ShellError> {
|
||||
fn interface_prepare_pipeline_data_serializes_custom_values() -> Result<(), ShellError> {
|
||||
let interface = TestCase::new().engine().get_interface();
|
||||
|
||||
let data = interface.prepare_pipeline_data(PipelineData::Value(
|
||||
Value::test_custom_value(Box::new(expected_test_custom_value())),
|
||||
None,
|
||||
))?;
|
||||
let data = interface.prepare_pipeline_data(
|
||||
PipelineData::Value(
|
||||
Value::test_custom_value(Box::new(expected_test_custom_value())),
|
||||
None,
|
||||
),
|
||||
&(),
|
||||
)?;
|
||||
|
||||
let value = data
|
||||
.into_iter()
|
||||
@ -1117,6 +1120,7 @@ fn interface_prepare_pipeline_data_serializes_custom_values_in_streams() -> Resu
|
||||
expected_test_custom_value(),
|
||||
))]
|
||||
.into_pipeline_data(None),
|
||||
&(),
|
||||
)?;
|
||||
|
||||
let value = data
|
||||
@ -1161,6 +1165,10 @@ impl CustomValue for CantSerialize {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1171,6 +1179,7 @@ fn interface_prepare_pipeline_data_embeds_serialization_errors_in_streams() -> R
|
||||
let span = Span::new(40, 60);
|
||||
let data = interface.prepare_pipeline_data(
|
||||
[Value::custom(Box::new(CantSerialize::BadVariant), span)].into_pipeline_data(None),
|
||||
&(),
|
||||
)?;
|
||||
|
||||
let value = data
|
||||
|
@ -12,10 +12,11 @@ use crate::{
|
||||
PluginOutput, ProtocolInfo, StreamId, StreamMessage,
|
||||
},
|
||||
sequence::Sequence,
|
||||
util::with_custom_values_in,
|
||||
};
|
||||
use nu_protocol::{
|
||||
ast::Operator, IntoInterruptiblePipelineData, IntoSpanned, ListStream, PipelineData,
|
||||
PluginSignature, ShellError, Span, Spanned, Value,
|
||||
ast::Operator, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, ListStream,
|
||||
PipelineData, PluginSignature, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
@ -96,10 +97,28 @@ struct PluginCallState {
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
/// Channel to receive context on to be used if needed
|
||||
context_rx: Option<mpsc::Receiver<Context>>,
|
||||
/// Channel for plugin custom values that should be kept alive for the duration of the plugin
|
||||
/// call. The plugin custom values on this channel are never read, we just hold on to it to keep
|
||||
/// them in memory so they can be dropped at the end of the call. We hold the sender as well so
|
||||
/// we can generate the CurrentCallState.
|
||||
keep_plugin_custom_values: (
|
||||
mpsc::Sender<PluginCustomValue>,
|
||||
mpsc::Receiver<PluginCustomValue>,
|
||||
),
|
||||
/// Number of streams that still need to be read from the plugin call response
|
||||
remaining_streams_to_read: i32,
|
||||
}
|
||||
|
||||
impl Drop for PluginCallState {
|
||||
fn drop(&mut self) {
|
||||
// Clear the keep custom values channel, so drop notifications can be sent
|
||||
for value in self.keep_plugin_custom_values.1.try_iter() {
|
||||
log::trace!("Dropping custom value that was kept: {:?}", value);
|
||||
drop(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages reading and dispatching messages for [`PluginInterface`]s.
|
||||
///
|
||||
/// This is not a public API.
|
||||
@ -264,12 +283,20 @@ impl PluginInterfaceManager {
|
||||
msg: "Tried to spawn the fallback engine call handler more than once"
|
||||
.into(),
|
||||
})?;
|
||||
|
||||
// Generate the state needed to handle engine calls
|
||||
let current_call_state = CurrentCallState {
|
||||
context_tx: None,
|
||||
keep_plugin_custom_values_tx: Some(state.keep_plugin_custom_values.0.clone()),
|
||||
};
|
||||
|
||||
let handler = move || {
|
||||
// We receive on the thread so that we don't block the reader thread
|
||||
let mut context = context_rx
|
||||
.recv()
|
||||
.ok() // The plugin call won't send context if it's not required.
|
||||
.map(|c| c.0);
|
||||
|
||||
for msg in rx {
|
||||
// This thread only handles engine calls.
|
||||
match msg {
|
||||
@ -277,6 +304,7 @@ impl PluginInterfaceManager {
|
||||
if let Err(err) = interface.handle_engine_call(
|
||||
engine_call_id,
|
||||
engine_call,
|
||||
¤t_call_state,
|
||||
context.as_deref_mut(),
|
||||
) {
|
||||
log::warn!(
|
||||
@ -486,26 +514,35 @@ impl InterfaceManager for PluginInterfaceManager {
|
||||
result
|
||||
}
|
||||
PluginOutput::EngineCall { context, id, call } => {
|
||||
// Handle reading the pipeline data, if any
|
||||
let mut call = call.map_data(|input| {
|
||||
let ctrlc = self.get_ctrlc(context)?;
|
||||
self.read_pipeline_data(input, ctrlc.as_ref())
|
||||
});
|
||||
// Add source to any plugin custom values in the arguments
|
||||
if let Ok(EngineCall::EvalClosure {
|
||||
ref mut positional, ..
|
||||
}) = call
|
||||
{
|
||||
for arg in positional.iter_mut() {
|
||||
PluginCustomValue::add_source(arg, &self.state.source);
|
||||
}
|
||||
}
|
||||
let call = call
|
||||
// Handle reading the pipeline data, if any
|
||||
.map_data(|input| {
|
||||
let ctrlc = self.get_ctrlc(context)?;
|
||||
self.read_pipeline_data(input, ctrlc.as_ref())
|
||||
})
|
||||
// Do anything extra needed for each engine call setup
|
||||
.and_then(|mut engine_call| {
|
||||
match engine_call {
|
||||
EngineCall::EvalClosure {
|
||||
ref mut positional, ..
|
||||
} => {
|
||||
for arg in positional.iter_mut() {
|
||||
// Add source to any plugin custom values in the arguments
|
||||
PluginCustomValue::add_source_in(arg, &self.state.source)?;
|
||||
}
|
||||
Ok(engine_call)
|
||||
}
|
||||
_ => Ok(engine_call),
|
||||
}
|
||||
});
|
||||
match call {
|
||||
Ok(call) => self.send_engine_call(context, id, call),
|
||||
// If there was an error with setting up the call, just write the error
|
||||
Err(err) => self
|
||||
.get_interface()
|
||||
.write_engine_call_response(id, EngineCallResponse::Error(err)),
|
||||
Err(err) => self.get_interface().write_engine_call_response(
|
||||
id,
|
||||
EngineCallResponse::Error(err),
|
||||
&CurrentCallState::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -519,14 +556,17 @@ impl InterfaceManager for PluginInterfaceManager {
|
||||
// Add source to any values
|
||||
match data {
|
||||
PipelineData::Value(ref mut value, _) => {
|
||||
PluginCustomValue::add_source(value, &self.state.source);
|
||||
with_custom_values_in(value, |custom_value| {
|
||||
PluginCustomValue::add_source(custom_value.item, &self.state.source);
|
||||
Ok::<_, ShellError>(())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
PipelineData::ListStream(ListStream { stream, ctrlc, .. }, meta) => {
|
||||
let source = self.state.source.clone();
|
||||
Ok(stream
|
||||
.map(move |mut value| {
|
||||
PluginCustomValue::add_source(&mut value, &source);
|
||||
let _ = PluginCustomValue::add_source_in(&mut value, &source);
|
||||
value
|
||||
})
|
||||
.into_pipeline_data_with_metadata(meta, ctrlc))
|
||||
@ -581,11 +621,12 @@ impl PluginInterface {
|
||||
&self,
|
||||
id: EngineCallId,
|
||||
response: EngineCallResponse<PipelineData>,
|
||||
state: &CurrentCallState,
|
||||
) -> Result<(), ShellError> {
|
||||
// Set up any stream if necessary
|
||||
let mut writer = None;
|
||||
let response = response.map_data(|data| {
|
||||
let (data_header, data_writer) = self.init_write_pipeline_data(data)?;
|
||||
let (data_header, data_writer) = self.init_write_pipeline_data(data, state)?;
|
||||
writer = Some(data_writer);
|
||||
Ok(data_header)
|
||||
})?;
|
||||
@ -602,22 +643,26 @@ impl PluginInterface {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a plugin call message. Returns the writer for the stream, and the receiver for
|
||||
/// messages - i.e. response and engine calls - related to the plugin call
|
||||
/// Write a plugin call message. Returns the writer for the stream.
|
||||
fn write_plugin_call(
|
||||
&self,
|
||||
call: PluginCall<PipelineData>,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
context_rx: mpsc::Receiver<Context>,
|
||||
) -> Result<
|
||||
(
|
||||
PipelineDataWriter<Self>,
|
||||
mpsc::Receiver<ReceivedPluginCallMessage>,
|
||||
),
|
||||
ShellError,
|
||||
> {
|
||||
mut call: PluginCall<PipelineData>,
|
||||
context: Option<&dyn PluginExecutionContext>,
|
||||
) -> Result<WritePluginCallResult, ShellError> {
|
||||
let id = self.state.plugin_call_id_sequence.next()?;
|
||||
let ctrlc = context.and_then(|c| c.ctrlc().cloned());
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let (context_tx, context_rx) = mpsc::channel();
|
||||
let keep_plugin_custom_values = mpsc::channel();
|
||||
|
||||
// Set up the state that will stay alive during the call.
|
||||
let state = CurrentCallState {
|
||||
context_tx: Some(context_tx),
|
||||
keep_plugin_custom_values_tx: Some(keep_plugin_custom_values.0.clone()),
|
||||
};
|
||||
|
||||
// Prepare the call with the state.
|
||||
state.prepare_plugin_call(&mut call, &self.state.source)?;
|
||||
|
||||
// Convert the call into one with a header and handle the stream, if necessary
|
||||
let (call, writer) = match call {
|
||||
@ -630,8 +675,8 @@ impl PluginInterface {
|
||||
mut call,
|
||||
input,
|
||||
}) => {
|
||||
verify_call_args(&mut call, &self.state.source)?;
|
||||
let (header, writer) = self.init_write_pipeline_data(input)?;
|
||||
state.prepare_call_args(&mut call, &self.state.source)?;
|
||||
let (header, writer) = self.init_write_pipeline_data(input, &state)?;
|
||||
(
|
||||
PluginCall::Run(CallInfo {
|
||||
name,
|
||||
@ -652,6 +697,7 @@ impl PluginInterface {
|
||||
sender: Some(tx),
|
||||
ctrlc,
|
||||
context_rx: Some(context_rx),
|
||||
keep_plugin_custom_values,
|
||||
remaining_streams_to_read: 0,
|
||||
},
|
||||
))
|
||||
@ -659,9 +705,9 @@ impl PluginInterface {
|
||||
error: format!("Plugin `{}` closed unexpectedly", self.state.source.name()),
|
||||
msg: "can't complete this operation because the plugin is closed".into(),
|
||||
span: match &call {
|
||||
PluginCall::CustomValueOp(value, _) => Some(value.span),
|
||||
PluginCall::Run(info) => Some(info.call.head),
|
||||
_ => None,
|
||||
PluginCall::Signature => None,
|
||||
PluginCall::Run(CallInfo { call, .. }) => Some(call.head),
|
||||
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
||||
},
|
||||
help: Some(format!(
|
||||
"the plugin may have experienced an error. Try registering the plugin again \
|
||||
@ -679,11 +725,22 @@ impl PluginInterface {
|
||||
inner: vec![],
|
||||
})?;
|
||||
|
||||
// Starting a plugin call adds a lock on the GC. Locks are not added for streams being read
|
||||
// by the plugin, so the plugin would have to explicitly tell us if it expects to stay alive
|
||||
// while reading streams in the background after the response ends.
|
||||
if let Some(ref gc) = self.gc {
|
||||
gc.increment_locks(1);
|
||||
}
|
||||
|
||||
// Write request
|
||||
self.write(PluginInput::Call(id, call))?;
|
||||
self.flush()?;
|
||||
|
||||
Ok((writer, rx))
|
||||
Ok(WritePluginCallResult {
|
||||
receiver: rx,
|
||||
writer,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read the channel for plugin call messages and handle them until the response is received.
|
||||
@ -691,7 +748,7 @@ impl PluginInterface {
|
||||
&self,
|
||||
rx: mpsc::Receiver<ReceivedPluginCallMessage>,
|
||||
mut context: Option<&mut (dyn PluginExecutionContext + '_)>,
|
||||
context_tx: mpsc::Sender<Context>,
|
||||
state: CurrentCallState,
|
||||
) -> Result<PluginCallResponse<PipelineData>, ShellError> {
|
||||
// Handle message from receiver
|
||||
for msg in rx {
|
||||
@ -700,7 +757,9 @@ impl PluginInterface {
|
||||
if resp.has_stream() {
|
||||
// If the response has a stream, we need to register the context
|
||||
if let Some(context) = context {
|
||||
let _ = context_tx.send(Context(context.boxed()));
|
||||
if let Some(ref context_tx) = state.context_tx {
|
||||
let _ = context_tx.send(Context(context.boxed()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(resp);
|
||||
@ -709,7 +768,12 @@ impl PluginInterface {
|
||||
return Err(err);
|
||||
}
|
||||
ReceivedPluginCallMessage::EngineCall(engine_call_id, engine_call) => {
|
||||
self.handle_engine_call(engine_call_id, engine_call, context.as_deref_mut())?;
|
||||
self.handle_engine_call(
|
||||
engine_call_id,
|
||||
engine_call,
|
||||
&state,
|
||||
context.as_deref_mut(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -724,6 +788,7 @@ impl PluginInterface {
|
||||
&self,
|
||||
engine_call_id: EngineCallId,
|
||||
engine_call: EngineCall<PipelineData>,
|
||||
state: &CurrentCallState,
|
||||
context: Option<&mut (dyn PluginExecutionContext + '_)>,
|
||||
) -> Result<(), ShellError> {
|
||||
let resp =
|
||||
@ -732,7 +797,7 @@ impl PluginInterface {
|
||||
let mut writer = None;
|
||||
let resp = resp
|
||||
.map_data(|data| {
|
||||
let (data_header, data_writer) = self.init_write_pipeline_data(data)?;
|
||||
let (data_header, data_writer) = self.init_write_pipeline_data(data, state)?;
|
||||
writer = Some(data_writer);
|
||||
Ok(data_header)
|
||||
})
|
||||
@ -762,26 +827,12 @@ impl PluginInterface {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
// Starting a plugin call adds a lock on the GC. Locks are not added for streams being read
|
||||
// by the plugin, so the plugin would have to explicitly tell us if it expects to stay alive
|
||||
// while reading streams in the background after the response ends.
|
||||
if let Some(ref gc) = self.gc {
|
||||
gc.increment_locks(1);
|
||||
}
|
||||
|
||||
// Create the channel to send context on if needed
|
||||
let (context_tx, context_rx) = mpsc::channel();
|
||||
|
||||
let (writer, rx) = self.write_plugin_call(
|
||||
call,
|
||||
context.as_ref().and_then(|c| c.ctrlc().cloned()),
|
||||
context_rx,
|
||||
)?;
|
||||
let result = self.write_plugin_call(call, context.as_deref())?;
|
||||
|
||||
// Finish writing stream in the background
|
||||
writer.write_background()?;
|
||||
result.writer.write_background()?;
|
||||
|
||||
self.receive_plugin_call_response(rx, context, context_tx)
|
||||
self.receive_plugin_call_response(result.receiver, context, result.state)
|
||||
}
|
||||
|
||||
/// Get the command signatures from the plugin.
|
||||
@ -858,9 +909,8 @@ impl PluginInterface {
|
||||
pub fn custom_value_partial_cmp(
|
||||
&self,
|
||||
value: PluginCustomValue,
|
||||
mut other_value: Value,
|
||||
other_value: Value,
|
||||
) -> Result<Option<Ordering>, ShellError> {
|
||||
PluginCustomValue::verify_source(&mut other_value, &self.state.source)?;
|
||||
// Note: the protocol is always designed to have a span with the custom value, but this
|
||||
// operation doesn't support one.
|
||||
let call = PluginCall::CustomValueOp(
|
||||
@ -881,9 +931,8 @@ impl PluginInterface {
|
||||
&self,
|
||||
left: Spanned<PluginCustomValue>,
|
||||
operator: Spanned<Operator>,
|
||||
mut right: Value,
|
||||
right: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
PluginCustomValue::verify_source(&mut right, &self.state.source)?;
|
||||
self.custom_value_op_expecting_value(left, CustomValueOp::Operation(operator, right))
|
||||
}
|
||||
|
||||
@ -899,22 +948,9 @@ impl PluginInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that custom values in call arguments come from the right source
|
||||
fn verify_call_args(
|
||||
call: &mut crate::EvaluatedCall,
|
||||
source: &Arc<PluginSource>,
|
||||
) -> Result<(), ShellError> {
|
||||
for arg in call.positional.iter_mut() {
|
||||
PluginCustomValue::verify_source(arg, source)?;
|
||||
}
|
||||
for arg in call.named.iter_mut().flat_map(|(_, arg)| arg.as_mut()) {
|
||||
PluginCustomValue::verify_source(arg, source)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Interface for PluginInterface {
|
||||
type Output = PluginInput;
|
||||
type DataContext = CurrentCallState;
|
||||
|
||||
fn write(&self, input: PluginInput) -> Result<(), ShellError> {
|
||||
log::trace!("to plugin: {:?}", input);
|
||||
@ -933,18 +969,23 @@ impl Interface for PluginInterface {
|
||||
&self.stream_manager_handle
|
||||
}
|
||||
|
||||
fn prepare_pipeline_data(&self, data: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
fn prepare_pipeline_data(
|
||||
&self,
|
||||
data: PipelineData,
|
||||
state: &CurrentCallState,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// Validate the destination of values in the pipeline data
|
||||
match data {
|
||||
PipelineData::Value(mut value, meta) => {
|
||||
PluginCustomValue::verify_source(&mut value, &self.state.source)?;
|
||||
state.prepare_value(&mut value, &self.state.source)?;
|
||||
Ok(PipelineData::Value(value, meta))
|
||||
}
|
||||
PipelineData::ListStream(ListStream { stream, ctrlc, .. }, meta) => {
|
||||
let source = self.state.source.clone();
|
||||
let state = state.clone();
|
||||
Ok(stream
|
||||
.map(move |mut value| {
|
||||
match PluginCustomValue::verify_source(&mut value, &source) {
|
||||
match state.prepare_value(&mut value, &source) {
|
||||
Ok(()) => value,
|
||||
// Put the error in the stream instead
|
||||
Err(err) => Value::error(err, value.span()),
|
||||
@ -972,6 +1013,113 @@ impl Drop for PluginInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return value of [`PluginInterface::write_plugin_call()`].
|
||||
#[must_use]
|
||||
struct WritePluginCallResult {
|
||||
/// Receiver for plugin call messages related to the written plugin call.
|
||||
receiver: mpsc::Receiver<ReceivedPluginCallMessage>,
|
||||
/// Writer for the stream, if any.
|
||||
writer: PipelineDataWriter<PluginInterface>,
|
||||
/// State to be kept for the duration of the plugin call.
|
||||
state: CurrentCallState,
|
||||
}
|
||||
|
||||
/// State related to the current plugin call being executed.
|
||||
///
|
||||
/// This is not a public API.
|
||||
#[doc(hidden)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CurrentCallState {
|
||||
/// Sender for context, which should be sent if the plugin call returned a stream so that
|
||||
/// engine calls may continue to be handled.
|
||||
context_tx: Option<mpsc::Sender<Context>>,
|
||||
/// Sender for a channel that retains plugin custom values that need to stay alive for the
|
||||
/// duration of a plugin call.
|
||||
keep_plugin_custom_values_tx: Option<mpsc::Sender<PluginCustomValue>>,
|
||||
}
|
||||
|
||||
impl CurrentCallState {
|
||||
/// Prepare a custom value for write. Verifies custom value origin, and keeps custom values that
|
||||
/// shouldn't be dropped immediately.
|
||||
fn prepare_custom_value(
|
||||
&self,
|
||||
custom_value: Spanned<&mut (dyn CustomValue + '_)>,
|
||||
source: &PluginSource,
|
||||
) -> Result<(), ShellError> {
|
||||
// Ensure we can use it
|
||||
PluginCustomValue::verify_source(custom_value.as_deref(), source)?;
|
||||
|
||||
// Check whether we need to keep it
|
||||
if let Some(keep_tx) = &self.keep_plugin_custom_values_tx {
|
||||
if let Some(custom_value) = custom_value
|
||||
.item
|
||||
.as_any()
|
||||
.downcast_ref::<PluginCustomValue>()
|
||||
{
|
||||
if custom_value.notify_on_drop() {
|
||||
log::trace!("Keeping custom value for drop later: {:?}", custom_value);
|
||||
keep_tx
|
||||
.send(custom_value.clone())
|
||||
.map_err(|_| ShellError::NushellFailed {
|
||||
msg: "Failed to custom value to keep channel".into(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare a value for write, including all contained custom values.
|
||||
fn prepare_value(&self, value: &mut Value, source: &PluginSource) -> Result<(), ShellError> {
|
||||
with_custom_values_in(value, |custom_value| {
|
||||
self.prepare_custom_value(custom_value, source)
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare call arguments for write.
|
||||
fn prepare_call_args(
|
||||
&self,
|
||||
call: &mut crate::EvaluatedCall,
|
||||
source: &PluginSource,
|
||||
) -> Result<(), ShellError> {
|
||||
for arg in call.positional.iter_mut() {
|
||||
self.prepare_value(arg, source)?;
|
||||
}
|
||||
for arg in call.named.iter_mut().flat_map(|(_, arg)| arg.as_mut()) {
|
||||
self.prepare_value(arg, source)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare a plugin call for write. Does not affect pipeline data, which is handled by
|
||||
/// `prepare_pipeline_data()` instead.
|
||||
fn prepare_plugin_call<D>(
|
||||
&self,
|
||||
call: &mut PluginCall<D>,
|
||||
source: &PluginSource,
|
||||
) -> Result<(), ShellError> {
|
||||
match call {
|
||||
PluginCall::Signature => Ok(()),
|
||||
PluginCall::Run(CallInfo { call, .. }) => self.prepare_call_args(call, source),
|
||||
PluginCall::CustomValueOp(custom_value, op) => {
|
||||
// `source` isn't present on Dropped.
|
||||
if !matches!(op, CustomValueOp::Dropped) {
|
||||
self.prepare_custom_value(custom_value.as_mut().map(|r| r as &mut _), source)?;
|
||||
}
|
||||
// Handle anything within the op.
|
||||
match op {
|
||||
CustomValueOp::ToBaseValue => Ok(()),
|
||||
CustomValueOp::FollowPathInt(_) => Ok(()),
|
||||
CustomValueOp::FollowPathString(_) => Ok(()),
|
||||
CustomValueOp::PartialCmp(value) => self.prepare_value(value, source),
|
||||
CustomValueOp::Operation(_, value) => self.prepare_value(value, source),
|
||||
CustomValueOp::Dropped => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an engine call.
|
||||
pub(crate) fn handle_engine_call(
|
||||
call: EngineCall<PipelineData>,
|
||||
|
@ -4,11 +4,14 @@ use super::{
|
||||
use crate::{
|
||||
plugin::{
|
||||
context::PluginExecutionBogusContext,
|
||||
interface::{test_util::TestCase, Interface, InterfaceManager},
|
||||
interface::{plugin::CurrentCallState, test_util::TestCase, Interface, InterfaceManager},
|
||||
PluginSource,
|
||||
},
|
||||
protocol::{
|
||||
test_util::{expected_test_custom_value, test_plugin_custom_value},
|
||||
test_util::{
|
||||
expected_test_custom_value, test_plugin_custom_value,
|
||||
test_plugin_custom_value_with_source,
|
||||
},
|
||||
CallInfo, CustomValueOp, EngineCall, EngineCallResponse, ExternalStreamInfo,
|
||||
ListStreamInfo, PipelineDataHeader, PluginCall, PluginCallId, PluginCustomValue,
|
||||
PluginInput, Protocol, ProtocolInfo, RawStreamInfo, StreamData, StreamMessage,
|
||||
@ -16,10 +19,16 @@ use crate::{
|
||||
EvaluatedCall, PluginCallResponse, PluginOutput,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::Closure, IntoInterruptiblePipelineData, PipelineData, PluginSignature, ShellError,
|
||||
Span, Spanned, Value,
|
||||
ast::{Math, Operator},
|
||||
engine::Closure,
|
||||
CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData, PluginSignature,
|
||||
ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::{mpsc, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
|
||||
#[test]
|
||||
fn manager_consume_all_consumes_messages() -> Result<(), ShellError> {
|
||||
@ -186,6 +195,7 @@ fn fake_plugin_call(
|
||||
sender: Some(tx),
|
||||
ctrlc: None,
|
||||
context_rx: None,
|
||||
keep_plugin_custom_values: mpsc::channel(),
|
||||
remaining_streams_to_read: 0,
|
||||
},
|
||||
);
|
||||
@ -488,6 +498,7 @@ fn manager_handle_engine_call_after_response_received() -> Result<(), ShellError
|
||||
sender: None,
|
||||
ctrlc: None,
|
||||
context_rx: Some(context_rx),
|
||||
keep_plugin_custom_values: mpsc::channel(),
|
||||
remaining_streams_to_read: 1,
|
||||
},
|
||||
);
|
||||
@ -551,6 +562,7 @@ fn manager_send_plugin_call_response_removes_context_only_if_no_streams_to_read(
|
||||
sender: None,
|
||||
ctrlc: None,
|
||||
context_rx: None,
|
||||
keep_plugin_custom_values: mpsc::channel(),
|
||||
remaining_streams_to_read: n as i32,
|
||||
},
|
||||
);
|
||||
@ -584,6 +596,7 @@ fn manager_consume_stream_end_removes_context_only_if_last_stream() -> Result<()
|
||||
sender: None,
|
||||
ctrlc: None,
|
||||
context_rx: None,
|
||||
keep_plugin_custom_values: mpsc::channel(),
|
||||
remaining_streams_to_read: n as i32,
|
||||
},
|
||||
);
|
||||
@ -734,7 +747,7 @@ fn interface_write_plugin_call_registers_subscription() -> Result<(), ShellError
|
||||
);
|
||||
|
||||
let interface = manager.get_interface();
|
||||
let _ = interface.write_plugin_call(PluginCall::Signature, None, mpsc::channel().1)?;
|
||||
let _ = interface.write_plugin_call(PluginCall::Signature, None)?;
|
||||
|
||||
manager.receive_plugin_call_subscriptions();
|
||||
assert!(!manager.plugin_call_states.is_empty(), "not registered");
|
||||
@ -747,9 +760,8 @@ fn interface_write_plugin_call_writes_signature() -> Result<(), ShellError> {
|
||||
let manager = test.plugin("test");
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let (writer, _) =
|
||||
interface.write_plugin_call(PluginCall::Signature, None, mpsc::channel().1)?;
|
||||
writer.write()?;
|
||||
let result = interface.write_plugin_call(PluginCall::Signature, None)?;
|
||||
result.writer.write()?;
|
||||
|
||||
let written = test.next_written().expect("nothing written");
|
||||
match written {
|
||||
@ -768,18 +780,17 @@ fn interface_write_plugin_call_writes_custom_value_op() -> Result<(), ShellError
|
||||
let manager = test.plugin("test");
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let (writer, _) = interface.write_plugin_call(
|
||||
let result = interface.write_plugin_call(
|
||||
PluginCall::CustomValueOp(
|
||||
Spanned {
|
||||
item: test_plugin_custom_value(),
|
||||
item: test_plugin_custom_value_with_source(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
CustomValueOp::ToBaseValue,
|
||||
),
|
||||
None,
|
||||
mpsc::channel().1,
|
||||
)?;
|
||||
writer.write()?;
|
||||
result.writer.write()?;
|
||||
|
||||
let written = test.next_written().expect("nothing written");
|
||||
match written {
|
||||
@ -801,7 +812,7 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
||||
let manager = test.plugin("test");
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let (writer, _) = interface.write_plugin_call(
|
||||
let result = interface.write_plugin_call(
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "foo".into(),
|
||||
call: EvaluatedCall {
|
||||
@ -812,9 +823,8 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
||||
input: PipelineData::Value(Value::test_int(-1), None),
|
||||
}),
|
||||
None,
|
||||
mpsc::channel().1,
|
||||
)?;
|
||||
writer.write()?;
|
||||
result.writer.write()?;
|
||||
|
||||
let written = test.next_written().expect("nothing written");
|
||||
match written {
|
||||
@ -840,7 +850,7 @@ fn interface_write_plugin_call_writes_run_with_stream_input() -> Result<(), Shel
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let values = vec![Value::test_int(1), Value::test_int(2)];
|
||||
let (writer, _) = interface.write_plugin_call(
|
||||
let result = interface.write_plugin_call(
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "foo".into(),
|
||||
call: EvaluatedCall {
|
||||
@ -851,9 +861,8 @@ fn interface_write_plugin_call_writes_run_with_stream_input() -> Result<(), Shel
|
||||
input: values.clone().into_pipeline_data(None),
|
||||
}),
|
||||
None,
|
||||
mpsc::channel().1,
|
||||
)?;
|
||||
writer.write()?;
|
||||
result.writer.write()?;
|
||||
|
||||
let written = test.next_written().expect("nothing written");
|
||||
let info = match written {
|
||||
@ -914,7 +923,7 @@ fn interface_receive_plugin_call_receives_response() -> Result<(), ShellError> {
|
||||
.expect("failed to send on new channel");
|
||||
drop(tx); // so we don't deadlock on recv()
|
||||
|
||||
let response = interface.receive_plugin_call_response(rx, None, mpsc::channel().0)?;
|
||||
let response = interface.receive_plugin_call_response(rx, None, CurrentCallState::default())?;
|
||||
assert!(
|
||||
matches!(response, PluginCallResponse::Signature(_)),
|
||||
"wrong response: {response:?}"
|
||||
@ -937,7 +946,7 @@ fn interface_receive_plugin_call_receives_error() -> Result<(), ShellError> {
|
||||
drop(tx); // so we don't deadlock on recv()
|
||||
|
||||
let error = interface
|
||||
.receive_plugin_call_response(rx, None, mpsc::channel().0)
|
||||
.receive_plugin_call_response(rx, None, CurrentCallState::default())
|
||||
.expect_err("did not receive error");
|
||||
assert!(
|
||||
matches!(error, ShellError::ExternalNotSupported { .. }),
|
||||
@ -966,7 +975,7 @@ fn interface_receive_plugin_call_handles_engine_call() -> Result<(), ShellError>
|
||||
// an error, but it should still do the engine call
|
||||
drop(tx);
|
||||
interface
|
||||
.receive_plugin_call_response(rx, Some(&mut context), mpsc::channel().0)
|
||||
.receive_plugin_call_response(rx, Some(&mut context), CurrentCallState::default())
|
||||
.expect_err("no error even though there was no response");
|
||||
|
||||
// Check for the engine call response output
|
||||
@ -1083,7 +1092,7 @@ fn interface_custom_value_to_base_value() -> Result<(), ShellError> {
|
||||
});
|
||||
|
||||
let result = interface.custom_value_to_base_value(Spanned {
|
||||
item: test_plugin_custom_value(),
|
||||
item: test_plugin_custom_value_with_source(),
|
||||
span: Span::test_data(),
|
||||
})?;
|
||||
|
||||
@ -1108,8 +1117,9 @@ fn normal_values(interface: &PluginInterface) -> Vec<Value> {
|
||||
#[test]
|
||||
fn interface_prepare_pipeline_data_accepts_normal_values() -> Result<(), ShellError> {
|
||||
let interface = TestCase::new().plugin("test").get_interface();
|
||||
let state = CurrentCallState::default();
|
||||
for value in normal_values(&interface) {
|
||||
match interface.prepare_pipeline_data(PipelineData::Value(value.clone(), None)) {
|
||||
match interface.prepare_pipeline_data(PipelineData::Value(value.clone(), None), &state) {
|
||||
Ok(data) => assert_eq!(
|
||||
value.get_type(),
|
||||
data.into_value(Span::test_data()).get_type()
|
||||
@ -1124,7 +1134,8 @@ fn interface_prepare_pipeline_data_accepts_normal_values() -> Result<(), ShellEr
|
||||
fn interface_prepare_pipeline_data_accepts_normal_streams() -> Result<(), ShellError> {
|
||||
let interface = TestCase::new().plugin("test").get_interface();
|
||||
let values = normal_values(&interface);
|
||||
let data = interface.prepare_pipeline_data(values.clone().into_pipeline_data(None))?;
|
||||
let state = CurrentCallState::default();
|
||||
let data = interface.prepare_pipeline_data(values.clone().into_pipeline_data(None), &state)?;
|
||||
|
||||
let mut count = 0;
|
||||
for (expected_value, actual_value) in values.iter().zip(data) {
|
||||
@ -1168,8 +1179,9 @@ fn bad_custom_values() -> Vec<Value> {
|
||||
#[test]
|
||||
fn interface_prepare_pipeline_data_rejects_bad_custom_value() -> Result<(), ShellError> {
|
||||
let interface = TestCase::new().plugin("test").get_interface();
|
||||
let state = CurrentCallState::default();
|
||||
for value in bad_custom_values() {
|
||||
match interface.prepare_pipeline_data(PipelineData::Value(value.clone(), None)) {
|
||||
match interface.prepare_pipeline_data(PipelineData::Value(value.clone(), None), &state) {
|
||||
Err(err) => match err {
|
||||
ShellError::CustomValueIncorrectForPlugin { .. } => (),
|
||||
_ => panic!("expected error type CustomValueIncorrectForPlugin, but got {err:?}"),
|
||||
@ -1185,7 +1197,8 @@ fn interface_prepare_pipeline_data_rejects_bad_custom_value_in_a_stream() -> Res
|
||||
{
|
||||
let interface = TestCase::new().plugin("test").get_interface();
|
||||
let values = bad_custom_values();
|
||||
let data = interface.prepare_pipeline_data(values.clone().into_pipeline_data(None))?;
|
||||
let state = CurrentCallState::default();
|
||||
let data = interface.prepare_pipeline_data(values.clone().into_pipeline_data(None), &state)?;
|
||||
|
||||
let mut count = 0;
|
||||
for value in data {
|
||||
@ -1199,3 +1212,297 @@ fn interface_prepare_pipeline_data_rejects_bad_custom_value_in_a_stream() -> Res
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_custom_value_verifies_source() {
|
||||
let span = Span::test_data();
|
||||
let source = Arc::new(PluginSource::new_fake("test"));
|
||||
|
||||
let mut val = test_plugin_custom_value();
|
||||
assert!(CurrentCallState::default()
|
||||
.prepare_custom_value(
|
||||
Spanned {
|
||||
item: &mut val,
|
||||
span,
|
||||
},
|
||||
&source
|
||||
)
|
||||
.is_err());
|
||||
|
||||
let mut val = test_plugin_custom_value().with_source(Some(source.clone()));
|
||||
assert!(CurrentCallState::default()
|
||||
.prepare_custom_value(
|
||||
Spanned {
|
||||
item: &mut val,
|
||||
span,
|
||||
},
|
||||
&source
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct DropCustomVal;
|
||||
#[typetag::serde]
|
||||
impl CustomValue for DropCustomVal {
|
||||
fn clone_value(&self, _span: Span) -> Value {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> String {
|
||||
"DropCustomVal".into()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, _span: Span) -> Result<Value, ShellError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn notify_plugin_on_drop(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_custom_value_sends_to_keep_channel_if_drop_notify() -> Result<(), ShellError> {
|
||||
let span = Span::test_data();
|
||||
let source = Arc::new(PluginSource::new_fake("test"));
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let state = CurrentCallState {
|
||||
context_tx: None,
|
||||
keep_plugin_custom_values_tx: Some(tx),
|
||||
};
|
||||
// Try with a custom val that has drop check set
|
||||
let mut drop_val = PluginCustomValue::serialize_from_custom_value(&DropCustomVal, span)?
|
||||
.with_source(Some(source.clone()));
|
||||
state.prepare_custom_value(
|
||||
Spanned {
|
||||
item: &mut drop_val,
|
||||
span,
|
||||
},
|
||||
&source,
|
||||
)?;
|
||||
// Check that the custom value was actually sent
|
||||
assert!(rx.try_recv().is_ok());
|
||||
// Now try with one that doesn't have it
|
||||
let mut not_drop_val = test_plugin_custom_value().with_source(Some(source.clone()));
|
||||
state.prepare_custom_value(
|
||||
Spanned {
|
||||
item: &mut not_drop_val,
|
||||
span,
|
||||
},
|
||||
&source,
|
||||
)?;
|
||||
// Should not have been sent to the channel
|
||||
assert!(rx.try_recv().is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_plugin_call_run() {
|
||||
// Check that args are handled
|
||||
let span = Span::test_data();
|
||||
let source = Arc::new(PluginSource::new_fake("test"));
|
||||
let other_source = Arc::new(PluginSource::new_fake("other"));
|
||||
let cv_ok = test_plugin_custom_value()
|
||||
.with_source(Some(source.clone()))
|
||||
.into_value(span);
|
||||
let cv_bad = test_plugin_custom_value()
|
||||
.with_source(Some(other_source))
|
||||
.into_value(span);
|
||||
|
||||
let fixtures = [
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "".into(),
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![Value::test_int(4)],
|
||||
named: vec![("x".to_owned().into_spanned(span), Some(Value::test_int(6)))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
}),
|
||||
),
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "".into(),
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![cv_ok.clone()],
|
||||
named: vec![("ok".to_owned().into_spanned(span), Some(cv_ok.clone()))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
}),
|
||||
),
|
||||
(
|
||||
false, // should fail
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "".into(),
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![cv_bad.clone()],
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
}),
|
||||
),
|
||||
(
|
||||
false, // should fail
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "".into(),
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![],
|
||||
named: vec![("bad".to_owned().into_spanned(span), Some(cv_bad.clone()))],
|
||||
},
|
||||
input: PipelineData::Empty,
|
||||
}),
|
||||
),
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "".into(),
|
||||
call: EvaluatedCall {
|
||||
head: span,
|
||||
positional: vec![],
|
||||
named: vec![],
|
||||
},
|
||||
// Shouldn't check input - that happens somewhere else
|
||||
input: PipelineData::Value(cv_bad.clone(), None),
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
for (should_succeed, mut fixture) in fixtures {
|
||||
let result = CurrentCallState::default().prepare_plugin_call(&mut fixture, &source);
|
||||
if should_succeed {
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Expected success, but failed with {:?} on {fixture:#?}",
|
||||
result.unwrap_err(),
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Expected failure, but succeeded on {fixture:#?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_plugin_call_custom_value_op() {
|
||||
// Check behavior with custom value ops
|
||||
let span = Span::test_data();
|
||||
let source = Arc::new(PluginSource::new_fake("test"));
|
||||
let other_source = Arc::new(PluginSource::new_fake("other"));
|
||||
let cv_ok = test_plugin_custom_value().with_source(Some(source.clone()));
|
||||
let cv_ok_val = cv_ok.clone_value(span);
|
||||
let cv_bad = test_plugin_custom_value().with_source(Some(other_source));
|
||||
let cv_bad_val = cv_bad.clone_value(span);
|
||||
|
||||
let fixtures = [
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::CustomValueOp::<PipelineData>(
|
||||
Spanned {
|
||||
item: cv_ok.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::ToBaseValue,
|
||||
),
|
||||
),
|
||||
(
|
||||
false, // should fail
|
||||
PluginCall::CustomValueOp(
|
||||
Spanned {
|
||||
item: cv_bad.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::ToBaseValue,
|
||||
),
|
||||
),
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::CustomValueOp(
|
||||
Spanned {
|
||||
item: test_plugin_custom_value(),
|
||||
span,
|
||||
},
|
||||
// Dropped shouldn't check. We don't have a source set.
|
||||
CustomValueOp::Dropped,
|
||||
),
|
||||
),
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::CustomValueOp::<PipelineData>(
|
||||
Spanned {
|
||||
item: cv_ok.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::PartialCmp(cv_ok_val.clone()),
|
||||
),
|
||||
),
|
||||
(
|
||||
false, // should fail
|
||||
PluginCall::CustomValueOp(
|
||||
Spanned {
|
||||
item: cv_ok.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::PartialCmp(cv_bad_val.clone()),
|
||||
),
|
||||
),
|
||||
(
|
||||
true, // should succeed
|
||||
PluginCall::CustomValueOp::<PipelineData>(
|
||||
Spanned {
|
||||
item: cv_ok.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::Operation(
|
||||
Operator::Math(Math::Append).into_spanned(span),
|
||||
cv_ok_val.clone(),
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
false, // should fail
|
||||
PluginCall::CustomValueOp(
|
||||
Spanned {
|
||||
item: cv_ok.clone(),
|
||||
span,
|
||||
},
|
||||
CustomValueOp::Operation(
|
||||
Operator::Math(Math::Append).into_spanned(span),
|
||||
cv_bad_val.clone(),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (should_succeed, mut fixture) in fixtures {
|
||||
let result = CurrentCallState::default().prepare_plugin_call(&mut fixture, &source);
|
||||
if should_succeed {
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Expected success, but failed with {:?} on {fixture:#?}",
|
||||
result.unwrap_err(),
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Expected failure, but succeeded on {fixture:#?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ impl InterfaceManager for TestInterfaceManager {
|
||||
|
||||
impl Interface for TestInterface {
|
||||
type Output = PluginOutput;
|
||||
type DataContext = ();
|
||||
|
||||
fn write(&self, output: Self::Output) -> Result<(), ShellError> {
|
||||
self.test.write(&output)
|
||||
@ -99,7 +100,11 @@ impl Interface for TestInterface {
|
||||
&self.stream_manager_handle
|
||||
}
|
||||
|
||||
fn prepare_pipeline_data(&self, data: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
fn prepare_pipeline_data(
|
||||
&self,
|
||||
data: PipelineData,
|
||||
_context: &(),
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// Add an arbitrary check to the data to verify this is being called
|
||||
match data {
|
||||
PipelineData::Value(Value::Binary { .. }, None) => Err(ShellError::NushellFailed {
|
||||
@ -318,7 +323,7 @@ fn write_pipeline_data_empty() -> Result<(), ShellError> {
|
||||
let manager = TestInterfaceManager::new(&test);
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let (header, writer) = interface.init_write_pipeline_data(PipelineData::Empty)?;
|
||||
let (header, writer) = interface.init_write_pipeline_data(PipelineData::Empty, &())?;
|
||||
|
||||
assert!(matches!(header, PipelineDataHeader::Empty));
|
||||
|
||||
@ -340,7 +345,7 @@ fn write_pipeline_data_value() -> Result<(), ShellError> {
|
||||
let value = Value::test_int(7);
|
||||
|
||||
let (header, writer) =
|
||||
interface.init_write_pipeline_data(PipelineData::Value(value.clone(), None))?;
|
||||
interface.init_write_pipeline_data(PipelineData::Value(value.clone(), None), &())?;
|
||||
|
||||
match header {
|
||||
PipelineDataHeader::Value(read_value) => assert_eq!(value, read_value),
|
||||
@ -365,7 +370,7 @@ fn write_pipeline_data_prepared_properly() {
|
||||
// Sending a binary should be an error in our test scenario
|
||||
let value = Value::test_binary(vec![7, 8]);
|
||||
|
||||
match interface.init_write_pipeline_data(PipelineData::Value(value, None)) {
|
||||
match interface.init_write_pipeline_data(PipelineData::Value(value, None), &()) {
|
||||
Ok(_) => panic!("prepare_pipeline_data was not called"),
|
||||
Err(err) => {
|
||||
assert_eq!(
|
||||
@ -397,7 +402,7 @@ fn write_pipeline_data_list_stream() -> Result<(), ShellError> {
|
||||
None,
|
||||
);
|
||||
|
||||
let (header, writer) = interface.init_write_pipeline_data(pipe)?;
|
||||
let (header, writer) = interface.init_write_pipeline_data(pipe, &())?;
|
||||
|
||||
let info = match header {
|
||||
PipelineDataHeader::ListStream(info) => info,
|
||||
@ -472,7 +477,7 @@ fn write_pipeline_data_external_stream() -> Result<(), ShellError> {
|
||||
trim_end_newline: true,
|
||||
};
|
||||
|
||||
let (header, writer) = interface.init_write_pipeline_data(pipe)?;
|
||||
let (header, writer) = interface.init_write_pipeline_data(pipe, &())?;
|
||||
|
||||
let info = match header {
|
||||
PipelineDataHeader::ExternalStream(info) => info,
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::plugin::{PluginInterface, PluginSource};
|
||||
use nu_protocol::{ast::Operator, CustomValue, IntoSpanned, ShellError, Span, Value};
|
||||
use std::{cmp::Ordering, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
plugin::{PluginInterface, PluginSource},
|
||||
util::with_custom_values_in,
|
||||
};
|
||||
use nu_protocol::{ast::Operator, CustomValue, IntoSpanned, ShellError, Span, Spanned, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Ordering, convert::Infallible, sync::Arc};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -50,10 +54,16 @@ fn is_false(b: &bool) -> bool {
|
||||
!b
|
||||
}
|
||||
|
||||
impl PluginCustomValue {
|
||||
pub fn into_value(self, span: Span) -> Value {
|
||||
Value::custom(Box::new(self), span)
|
||||
}
|
||||
}
|
||||
|
||||
#[typetag::serde]
|
||||
impl CustomValue for PluginCustomValue {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::custom(Box::new(self.clone()), span)
|
||||
self.clone().into_value(span)
|
||||
}
|
||||
|
||||
fn type_name(&self) -> String {
|
||||
@ -127,6 +137,10 @@ impl CustomValue for PluginCustomValue {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginCustomValue {
|
||||
@ -164,11 +178,15 @@ impl PluginCustomValue {
|
||||
|
||||
/// Which plugin the custom value came from. This is not defined on the plugin side. The engine
|
||||
/// side is responsible for maintaining it, and it is not sent over the serialization boundary.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn source(&self) -> &Option<Arc<PluginSource>> {
|
||||
pub fn source(&self) -> &Option<Arc<PluginSource>> {
|
||||
&self.source
|
||||
}
|
||||
|
||||
/// Set the [`PluginSource`] for this [`PluginCustomValue`].
|
||||
pub fn set_source(&mut self, source: Option<Arc<PluginSource>>) {
|
||||
self.source = source;
|
||||
}
|
||||
|
||||
/// Create the [`PluginCustomValue`] with the given source.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn with_source(mut self, source: Option<Arc<PluginSource>>) -> PluginCustomValue {
|
||||
@ -234,84 +252,55 @@ impl PluginCustomValue {
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a [`PluginSource`] to all [`PluginCustomValue`]s within a value, recursively.
|
||||
pub fn add_source(value: &mut Value, source: &Arc<PluginSource>) {
|
||||
// This can't cause an error.
|
||||
let _: Result<(), Infallible> = value.recurse_mut(&mut |value| {
|
||||
let span = value.span();
|
||||
match value {
|
||||
// Set source on custom value
|
||||
Value::Custom { ref val, .. } => {
|
||||
if let Some(custom_value) = val.as_any().downcast_ref::<PluginCustomValue>() {
|
||||
// Since there's no `as_mut_any()`, we have to copy the whole thing
|
||||
let mut custom_value = custom_value.clone();
|
||||
custom_value.source = Some(source.clone());
|
||||
*value = Value::custom(Box::new(custom_value), span);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// LazyRecord could generate other values, but we shouldn't be receiving it anyway
|
||||
//
|
||||
// It's better to handle this as a bug
|
||||
Value::LazyRecord { .. } => unimplemented!("add_source for LazyRecord"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
});
|
||||
/// Add a [`PluginSource`] to the given [`CustomValue`] if it is a [`PluginCustomValue`].
|
||||
pub fn add_source(value: &mut dyn CustomValue, source: &Arc<PluginSource>) {
|
||||
if let Some(custom_value) = value.as_mut_any().downcast_mut::<PluginCustomValue>() {
|
||||
custom_value.set_source(Some(source.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that all [`CustomValue`]s present within the `value` are [`PluginCustomValue`]s that
|
||||
/// come from the given `source`, and return an error if not.
|
||||
/// Add a [`PluginSource`] to all [`PluginCustomValue`]s within the value, recursively.
|
||||
pub fn add_source_in(value: &mut Value, source: &Arc<PluginSource>) -> Result<(), ShellError> {
|
||||
with_custom_values_in(value, |custom_value| {
|
||||
Self::add_source(custom_value.item, source);
|
||||
Ok::<_, ShellError>(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Check that a [`CustomValue`] is a [`PluginCustomValue`] that come from the given `source`,
|
||||
/// and return an error if not.
|
||||
///
|
||||
/// This method will collapse `LazyRecord` in-place as necessary to make the guarantee,
|
||||
/// since `LazyRecord` could return something different the next time it is called.
|
||||
pub(crate) fn verify_source(
|
||||
value: &mut Value,
|
||||
value: Spanned<&dyn CustomValue>,
|
||||
source: &PluginSource,
|
||||
) -> Result<(), ShellError> {
|
||||
value.recurse_mut(&mut |value| {
|
||||
let span = value.span();
|
||||
match value {
|
||||
// Set source on custom value
|
||||
Value::Custom { val, .. } => {
|
||||
if let Some(custom_value) = val.as_any().downcast_ref::<PluginCustomValue>() {
|
||||
if custom_value
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|s| s.is_compatible(source))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ShellError::CustomValueIncorrectForPlugin {
|
||||
name: custom_value.name().to_owned(),
|
||||
span,
|
||||
dest_plugin: source.name().to_owned(),
|
||||
src_plugin: custom_value
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|s| s.name().to_owned()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Only PluginCustomValues can be sent
|
||||
Err(ShellError::CustomValueIncorrectForPlugin {
|
||||
name: val.type_name(),
|
||||
span,
|
||||
dest_plugin: source.name().to_owned(),
|
||||
src_plugin: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
// LazyRecord would be a problem for us, since it could return something else the
|
||||
// next time, and we have to collect it anyway to serialize it. Collect it in place,
|
||||
// and then verify the source of the result
|
||||
Value::LazyRecord { val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
if let Some(custom_value) = value.item.as_any().downcast_ref::<PluginCustomValue>() {
|
||||
if custom_value
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|s| s.is_compatible(source))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ShellError::CustomValueIncorrectForPlugin {
|
||||
name: custom_value.name().to_owned(),
|
||||
span: value.span,
|
||||
dest_plugin: source.name().to_owned(),
|
||||
src_plugin: custom_value.source.as_ref().map(|s| s.name().to_owned()),
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Only PluginCustomValues can be sent
|
||||
Err(ShellError::CustomValueIncorrectForPlugin {
|
||||
name: value.item.type_name(),
|
||||
span: value.span,
|
||||
dest_plugin: source.name().to_owned(),
|
||||
src_plugin: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert all plugin-native custom values to [`PluginCustomValue`] within the given `value`,
|
||||
|
@ -7,7 +7,8 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use nu_protocol::{
|
||||
ast::RangeInclusion, engine::Closure, record, CustomValue, Range, ShellError, Span, Value,
|
||||
ast::RangeInclusion, engine::Closure, record, CustomValue, IntoSpanned, Range, ShellError,
|
||||
Span, Value,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -42,10 +43,10 @@ fn expected_serialize_output() -> Result<(), ShellError> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_source_at_root() -> Result<(), ShellError> {
|
||||
fn add_source_in_at_root() -> Result<(), ShellError> {
|
||||
let mut val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
let source = Arc::new(PluginSource::new_fake("foo"));
|
||||
PluginCustomValue::add_source(&mut val, &source);
|
||||
PluginCustomValue::add_source_in(&mut val, &source)?;
|
||||
|
||||
let custom_value = val.as_custom_value()?;
|
||||
let plugin_custom_value: &PluginCustomValue = custom_value
|
||||
@ -78,7 +79,7 @@ fn check_range_custom_values(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_source_nested_range() -> Result<(), ShellError> {
|
||||
fn add_source_in_nested_range() -> Result<(), ShellError> {
|
||||
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
let mut val = Value::test_range(Range {
|
||||
from: orig_custom_val.clone(),
|
||||
@ -87,7 +88,7 @@ fn add_source_nested_range() -> Result<(), ShellError> {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
});
|
||||
let source = Arc::new(PluginSource::new_fake("foo"));
|
||||
PluginCustomValue::add_source(&mut val, &source);
|
||||
PluginCustomValue::add_source_in(&mut val, &source)?;
|
||||
|
||||
check_range_custom_values(&val, |name, custom_value| {
|
||||
let plugin_custom_value: &PluginCustomValue = custom_value
|
||||
@ -122,14 +123,14 @@ fn check_record_custom_values(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_source_nested_record() -> Result<(), ShellError> {
|
||||
fn add_source_in_nested_record() -> Result<(), ShellError> {
|
||||
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
let mut val = Value::test_record(record! {
|
||||
"foo" => orig_custom_val.clone(),
|
||||
"bar" => orig_custom_val.clone(),
|
||||
});
|
||||
let source = Arc::new(PluginSource::new_fake("foo"));
|
||||
PluginCustomValue::add_source(&mut val, &source);
|
||||
PluginCustomValue::add_source_in(&mut val, &source)?;
|
||||
|
||||
check_record_custom_values(&val, &["foo", "bar"], |key, custom_value| {
|
||||
let plugin_custom_value: &PluginCustomValue = custom_value
|
||||
@ -164,11 +165,11 @@ fn check_list_custom_values(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_source_nested_list() -> Result<(), ShellError> {
|
||||
fn add_source_in_nested_list() -> Result<(), ShellError> {
|
||||
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
let mut val = Value::test_list(vec![orig_custom_val.clone(), orig_custom_val.clone()]);
|
||||
let source = Arc::new(PluginSource::new_fake("foo"));
|
||||
PluginCustomValue::add_source(&mut val, &source);
|
||||
PluginCustomValue::add_source_in(&mut val, &source)?;
|
||||
|
||||
check_list_custom_values(&val, 0..=1, |index, custom_value| {
|
||||
let plugin_custom_value: &PluginCustomValue = custom_value
|
||||
@ -205,14 +206,14 @@ fn check_closure_custom_values(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_source_nested_closure() -> Result<(), ShellError> {
|
||||
fn add_source_in_nested_closure() -> Result<(), ShellError> {
|
||||
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
let mut val = Value::test_closure(Closure {
|
||||
block_id: 0,
|
||||
captures: vec![(0, orig_custom_val.clone()), (1, orig_custom_val.clone())],
|
||||
});
|
||||
let source = Arc::new(PluginSource::new_fake("foo"));
|
||||
PluginCustomValue::add_source(&mut val, &source);
|
||||
PluginCustomValue::add_source_in(&mut val, &source)?;
|
||||
|
||||
check_closure_custom_values(&val, 0..=1, |index, custom_value| {
|
||||
let plugin_custom_value: &PluginCustomValue = custom_value
|
||||
@ -231,21 +232,25 @@ fn add_source_nested_closure() -> Result<(), ShellError> {
|
||||
#[test]
|
||||
fn verify_source_error_message() -> Result<(), ShellError> {
|
||||
let span = Span::new(5, 7);
|
||||
let mut ok_val = Value::custom(Box::new(test_plugin_custom_value_with_source()), span);
|
||||
let mut native_val = Value::custom(Box::new(TestCustomValue(32)), span);
|
||||
let mut foreign_val = {
|
||||
let ok_val = test_plugin_custom_value_with_source();
|
||||
let native_val = TestCustomValue(32);
|
||||
let foreign_val = {
|
||||
let mut val = test_plugin_custom_value();
|
||||
val.source = Some(Arc::new(PluginSource::new_fake("other")));
|
||||
Value::custom(Box::new(val), span)
|
||||
val
|
||||
};
|
||||
let source = PluginSource::new_fake("test");
|
||||
|
||||
PluginCustomValue::verify_source(&mut ok_val, &source).expect("ok_val should be verified ok");
|
||||
PluginCustomValue::verify_source((&ok_val as &dyn CustomValue).into_spanned(span), &source)
|
||||
.expect("ok_val should be verified ok");
|
||||
|
||||
for (val, src_plugin) in [(&mut native_val, None), (&mut foreign_val, Some("other"))] {
|
||||
let error = PluginCustomValue::verify_source(val, &source).expect_err(&format!(
|
||||
"a custom value from {src_plugin:?} should result in an error"
|
||||
));
|
||||
for (val, src_plugin) in [
|
||||
(&native_val as &dyn CustomValue, None),
|
||||
(&foreign_val as &dyn CustomValue, Some("other")),
|
||||
] {
|
||||
let error = PluginCustomValue::verify_source(val.into_spanned(span), &source).expect_err(
|
||||
&format!("a custom value from {src_plugin:?} should result in an error"),
|
||||
);
|
||||
if let ShellError::CustomValueIncorrectForPlugin {
|
||||
name,
|
||||
span: err_span,
|
||||
@ -265,145 +270,6 @@ fn verify_source_error_message() -> Result<(), ShellError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_source_nested_range() -> Result<(), ShellError> {
|
||||
let native_val = Value::test_custom_value(Box::new(TestCustomValue(32)));
|
||||
let source = PluginSource::new_fake("test");
|
||||
for (name, mut val) in [
|
||||
(
|
||||
"from",
|
||||
Value::test_range(Range {
|
||||
from: native_val.clone(),
|
||||
incr: Value::test_nothing(),
|
||||
to: Value::test_nothing(),
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
}),
|
||||
),
|
||||
(
|
||||
"incr",
|
||||
Value::test_range(Range {
|
||||
from: Value::test_nothing(),
|
||||
incr: native_val.clone(),
|
||||
to: Value::test_nothing(),
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
}),
|
||||
),
|
||||
(
|
||||
"to",
|
||||
Value::test_range(Range {
|
||||
from: Value::test_nothing(),
|
||||
incr: Value::test_nothing(),
|
||||
to: native_val.clone(),
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
}),
|
||||
),
|
||||
] {
|
||||
PluginCustomValue::verify_source(&mut val, &source)
|
||||
.expect_err(&format!("error not generated on {name}"));
|
||||
}
|
||||
|
||||
let mut ok_range = Value::test_range(Range {
|
||||
from: Value::test_nothing(),
|
||||
incr: Value::test_nothing(),
|
||||
to: Value::test_nothing(),
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
});
|
||||
PluginCustomValue::verify_source(&mut ok_range, &source)
|
||||
.expect("ok_range should not generate error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_source_nested_record() -> Result<(), ShellError> {
|
||||
let native_val = Value::test_custom_value(Box::new(TestCustomValue(32)));
|
||||
let source = PluginSource::new_fake("test");
|
||||
for (name, mut val) in [
|
||||
(
|
||||
"first element foo",
|
||||
Value::test_record(record! {
|
||||
"foo" => native_val.clone(),
|
||||
"bar" => Value::test_nothing(),
|
||||
}),
|
||||
),
|
||||
(
|
||||
"second element bar",
|
||||
Value::test_record(record! {
|
||||
"foo" => Value::test_nothing(),
|
||||
"bar" => native_val.clone(),
|
||||
}),
|
||||
),
|
||||
] {
|
||||
PluginCustomValue::verify_source(&mut val, &source)
|
||||
.expect_err(&format!("error not generated on {name}"));
|
||||
}
|
||||
|
||||
let mut ok_record = Value::test_record(record! {"foo" => Value::test_nothing()});
|
||||
PluginCustomValue::verify_source(&mut ok_record, &source)
|
||||
.expect("ok_record should not generate error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_source_nested_list() -> Result<(), ShellError> {
|
||||
let native_val = Value::test_custom_value(Box::new(TestCustomValue(32)));
|
||||
let source = PluginSource::new_fake("test");
|
||||
for (name, mut val) in [
|
||||
(
|
||||
"first element",
|
||||
Value::test_list(vec![native_val.clone(), Value::test_nothing()]),
|
||||
),
|
||||
(
|
||||
"second element",
|
||||
Value::test_list(vec![Value::test_nothing(), native_val.clone()]),
|
||||
),
|
||||
] {
|
||||
PluginCustomValue::verify_source(&mut val, &source)
|
||||
.expect_err(&format!("error not generated on {name}"));
|
||||
}
|
||||
|
||||
let mut ok_list = Value::test_list(vec![Value::test_nothing()]);
|
||||
PluginCustomValue::verify_source(&mut ok_list, &source)
|
||||
.expect("ok_list should not generate error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_source_nested_closure() -> Result<(), ShellError> {
|
||||
let native_val = Value::test_custom_value(Box::new(TestCustomValue(32)));
|
||||
let source = PluginSource::new_fake("test");
|
||||
for (name, mut val) in [
|
||||
(
|
||||
"first capture",
|
||||
Value::test_closure(Closure {
|
||||
block_id: 0,
|
||||
captures: vec![(0, native_val.clone()), (1, Value::test_nothing())],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"second capture",
|
||||
Value::test_closure(Closure {
|
||||
block_id: 0,
|
||||
captures: vec![(0, Value::test_nothing()), (1, native_val.clone())],
|
||||
}),
|
||||
),
|
||||
] {
|
||||
PluginCustomValue::verify_source(&mut val, &source)
|
||||
.expect_err(&format!("error not generated on {name}"));
|
||||
}
|
||||
|
||||
let mut ok_closure = Value::test_closure(Closure {
|
||||
block_id: 0,
|
||||
captures: vec![(0, Value::test_nothing())],
|
||||
});
|
||||
PluginCustomValue::verify_source(&mut ok_closure, &source)
|
||||
.expect("ok_closure should not generate error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_in_root() -> Result<(), ShellError> {
|
||||
let span = Span::new(4, 10);
|
||||
|
@ -23,6 +23,10 @@ impl CustomValue for TestCustomValue {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test_plugin_custom_value() -> PluginCustomValue {
|
||||
|
@ -1,3 +1,5 @@
|
||||
mod mutable_cow;
|
||||
mod with_custom_values_in;
|
||||
|
||||
pub(crate) use mutable_cow::*;
|
||||
pub use with_custom_values_in::*;
|
||||
|
102
crates/nu-plugin/src/util/with_custom_values_in.rs
Normal file
102
crates/nu-plugin/src/util/with_custom_values_in.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use nu_protocol::{CustomValue, IntoSpanned, ShellError, Spanned, Value};
|
||||
|
||||
/// Do something with all [`CustomValue`]s recursively within a `Value`. This is not limited to
|
||||
/// plugin custom values.
|
||||
///
|
||||
/// `LazyRecord`s will be collected to plain values for completeness.
|
||||
pub fn with_custom_values_in<E>(
|
||||
value: &mut Value,
|
||||
mut f: impl FnMut(Spanned<&mut (dyn CustomValue + '_)>) -> Result<(), E>,
|
||||
) -> Result<(), E>
|
||||
where
|
||||
E: From<ShellError>,
|
||||
{
|
||||
value.recurse_mut(&mut |value| {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Custom { val, .. } => {
|
||||
// Operate on a CustomValue.
|
||||
f(val.as_mut().into_spanned(span))
|
||||
}
|
||||
// LazyRecord would be a problem for us, since it could return something else the
|
||||
// next time, and we have to collect it anyway to serialize it. Collect it in place,
|
||||
// and then use the result
|
||||
Value::LazyRecord { val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_custom_values() {
|
||||
use crate::protocol::test_util::test_plugin_custom_value;
|
||||
use nu_protocol::{ast::RangeInclusion, engine::Closure, record, LazyRecord, Range, Span};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Lazy;
|
||||
impl<'a> LazyRecord<'a> for Lazy {
|
||||
fn column_names(&'a self) -> Vec<&'a str> {
|
||||
vec!["custom", "plain"]
|
||||
}
|
||||
|
||||
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
||||
Ok(match column {
|
||||
"custom" => Value::test_custom_value(Box::new(test_plugin_custom_value())),
|
||||
"plain" => Value::test_int(42),
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
Span::test_data()
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::lazy_record(Box::new(self.clone()), span)
|
||||
}
|
||||
}
|
||||
|
||||
let mut cv = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
|
||||
let mut value = Value::test_record(record! {
|
||||
"bare" => cv.clone(),
|
||||
"list" => Value::test_list(vec![
|
||||
cv.clone(),
|
||||
Value::test_int(4),
|
||||
]),
|
||||
"closure" => Value::test_closure(
|
||||
Closure {
|
||||
block_id: 0,
|
||||
captures: vec![(0, cv.clone()), (1, Value::test_string("foo"))]
|
||||
}
|
||||
),
|
||||
"range" => Value::test_range(Range {
|
||||
from: cv.clone(),
|
||||
incr: cv.clone(),
|
||||
to: cv.clone(),
|
||||
inclusion: RangeInclusion::Inclusive
|
||||
}),
|
||||
"lazy" => Value::test_lazy_record(Box::new(Lazy)),
|
||||
});
|
||||
|
||||
// Do with_custom_values_in, and count the number of custom values found
|
||||
let mut found = 0;
|
||||
with_custom_values_in::<ShellError>(&mut value, |_| {
|
||||
found += 1;
|
||||
Ok(())
|
||||
})
|
||||
.expect("error");
|
||||
assert_eq!(7, found, "found in value");
|
||||
|
||||
// Try it on bare custom value too
|
||||
found = 0;
|
||||
with_custom_values_in::<ShellError>(&mut cv, |_| {
|
||||
found += 1;
|
||||
Ok(())
|
||||
})
|
||||
.expect("error");
|
||||
assert_eq!(1, found, "bare custom value didn't work");
|
||||
}
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-protocol"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,9 +13,9 @@ version = "0.92.0"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-system = { path = "../nu-system", version = "0.92.2" }
|
||||
|
||||
byte-unit = { version = "5.1", features = [ "serde" ] }
|
||||
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||
@ -37,7 +37,7 @@ plugin = ["serde_json"]
|
||||
serde_json = { workspace = true }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.92.2" }
|
||||
rstest = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
@ -125,9 +125,9 @@ impl Stack {
|
||||
unique_stack.env_vars = child.env_vars;
|
||||
unique_stack.env_hidden = child.env_hidden;
|
||||
unique_stack.active_overlays = child.active_overlays;
|
||||
for item in child.parent_deletions.into_iter() {
|
||||
unique_stack.vars.remove(item);
|
||||
}
|
||||
unique_stack
|
||||
.vars
|
||||
.retain(|(var, _)| !child.parent_deletions.contains(var));
|
||||
unique_stack
|
||||
}
|
||||
pub fn with_env(
|
||||
|
@ -473,7 +473,7 @@ pub enum ShellError {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// An error happened while tryin to create a range.
|
||||
/// An error happened while trying to create a range.
|
||||
///
|
||||
/// This can happen in various unexpected situations, for example if the range would loop forever (as would be the case with a 0-increment).
|
||||
///
|
||||
|
@ -1,13 +1,54 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use miette::SourceSpan;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A spanned area of interest, generic over what kind of thing is of interest
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Spanned<T> {
|
||||
pub item: T,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
/// Map to a spanned reference of the inner type, i.e. `Spanned<T> -> Spanned<&T>`.
|
||||
pub fn as_ref(&self) -> Spanned<&T> {
|
||||
Spanned {
|
||||
item: &self.item,
|
||||
span: self.span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map to a mutable reference of the inner type, i.e. `Spanned<T> -> Spanned<&mut T>`.
|
||||
pub fn as_mut(&mut self) -> Spanned<&mut T> {
|
||||
Spanned {
|
||||
item: &mut self.item,
|
||||
span: self.span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map to the result of [`.deref()`](std::ops::Deref::deref) on the inner type.
|
||||
///
|
||||
/// This can be used for example to turn `Spanned<Vec<T>>` into `Spanned<&[T]>`.
|
||||
pub fn as_deref(&self) -> Spanned<&<T as Deref>::Target>
|
||||
where
|
||||
T: Deref,
|
||||
{
|
||||
Spanned {
|
||||
item: self.item.deref(),
|
||||
span: self.span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the spanned item with a function.
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
|
||||
Spanned {
|
||||
item: f(self.item),
|
||||
span: self.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to create [`Spanned`] more ergonomically.
|
||||
pub trait IntoSpanned: Sized {
|
||||
/// Wrap items together with a span into [`Spanned`].
|
||||
|
@ -27,6 +27,9 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
|
||||
/// Any representation used to downcast object to its original type
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
|
||||
/// Any representation used to downcast object to its original type (mutable reference)
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any;
|
||||
|
||||
/// Follow cell path by numeric index (e.g. rows)
|
||||
fn follow_path_int(
|
||||
&self,
|
||||
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-std"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[dependencies]
|
||||
nu-parser = { version = "0.92.0", path = "../nu-parser" }
|
||||
nu-protocol = { version = "0.92.0", path = "../nu-protocol" }
|
||||
nu-engine = { version = "0.92.0", path = "../nu-engine" }
|
||||
nu-parser = { version = "0.92.2", path = "../nu-parser" }
|
||||
nu-protocol = { version = "0.92.2", path = "../nu-protocol" }
|
||||
nu-engine = { version = "0.92.2", path = "../nu-engine" }
|
||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||
|
||||
log = "0.4"
|
||||
|
@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
|
||||
description = "Nushell system querying"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
|
||||
name = "nu-system"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-table"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.92.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.92.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
fancy-regex = { workspace = true }
|
||||
tabled = { workspace = true, features = ["color"], default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.92.0" }
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.92.2" }
|
||||
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-term-grid"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
|
||||
unicode-width = { workspace = true }
|
||||
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-test-support"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-path = { path = "../nu-path", version = "0.92.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.92.2" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.92.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.92.2" }
|
||||
|
||||
num-format = { workspace = true }
|
||||
which = { workspace = true }
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-utils"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[bin]]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Nushell Config File
|
||||
#
|
||||
# version = "0.92.0"
|
||||
# version = "0.92.2"
|
||||
|
||||
# For more information on defining custom themes, see
|
||||
# https://www.nushell.sh/book/coloring_and_theming.html
|
||||
@ -825,23 +825,36 @@ $env.config = {
|
||||
mode: emacs
|
||||
event: { edit: capitalizechar }
|
||||
}
|
||||
# The *_system keybindings require terminal support to pass these
|
||||
# keybindings through to nushell and nushell compiled with the
|
||||
# `system-clipboard` feature
|
||||
# The following bindings with `*system` events require that Nushell has
|
||||
# been compiled with the `system-clipboard` feature.
|
||||
# This should be the case for Windows, macOS, and most Linux distributions
|
||||
# Not available for example on Android (termux)
|
||||
# If you want to use the system clipboard for visual selection or to
|
||||
# paste directly, uncomment the respective lines and replace the version
|
||||
# using the internal clipboard.
|
||||
{
|
||||
name: copy_selection_system
|
||||
name: copy_selection
|
||||
modifier: control_shift
|
||||
keycode: char_c
|
||||
mode: emacs
|
||||
event: { edit: copyselectionsystem }
|
||||
event: { edit: copyselection }
|
||||
# event: { edit: copyselectionsystem }
|
||||
}
|
||||
{
|
||||
name: cut_selection_system
|
||||
name: cut_selection
|
||||
modifier: control_shift
|
||||
keycode: char_x
|
||||
mode: emacs
|
||||
event: { edit: cutselectionsystem }
|
||||
event: { edit: cutselection }
|
||||
# event: { edit: cutselectionsystem }
|
||||
}
|
||||
# {
|
||||
# name: paste_system
|
||||
# modifier: control_shift
|
||||
# keycode: char_v
|
||||
# mode: emacs
|
||||
# event: { edit: pastesystem }
|
||||
# }
|
||||
{
|
||||
name: select_all
|
||||
modifier: control_shift
|
||||
@ -849,12 +862,5 @@ $env.config = {
|
||||
mode: emacs
|
||||
event: { edit: selectall }
|
||||
}
|
||||
{
|
||||
name: paste_system
|
||||
modifier: control_shift
|
||||
keycode: char_v
|
||||
mode: emacs
|
||||
event: { edit: pastesystem }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Nushell Environment Config File
|
||||
#
|
||||
# version = "0.92.0"
|
||||
# version = "0.92.2"
|
||||
|
||||
def create_left_prompt [] {
|
||||
let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) {
|
||||
|
@ -10,10 +10,10 @@ name = "nu_plugin_custom_values"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2", features = ["plugin"] }
|
||||
serde = { workspace = true, default-features = false }
|
||||
typetag = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.0" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.2" }
|
||||
|
@ -144,4 +144,8 @@ impl CustomValue for CoolCustomValue {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,10 @@ impl CustomValue for DropCheckValue {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn notify_plugin_on_drop(&self) -> bool {
|
||||
// This is what causes Nushell to let us know when the value is dropped
|
||||
true
|
||||
|
@ -42,6 +42,6 @@ impl SimplePluginCommand for Generate {
|
||||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("custom_values", crate::CustomValuePlugin.into())?
|
||||
PluginTest::new("custom_values", CustomValuePlugin::new().into())?
|
||||
.test_command_examples(&Generate)
|
||||
}
|
||||
|
@ -66,6 +66,6 @@ impl SimplePluginCommand for Generate2 {
|
||||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("custom_values", crate::CustomValuePlugin.into())?
|
||||
PluginTest::new("custom_values", crate::CustomValuePlugin::new().into())?
|
||||
.test_command_examples(&Generate2)
|
||||
}
|
||||
|
42
crates/nu_plugin_custom_values/src/handle_custom_value.rs
Normal file
42
crates/nu_plugin_custom_values/src/handle_custom_value.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use nu_protocol::{CustomValue, LabeledError, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// References a stored handle within the plugin
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HandleCustomValue(pub u64);
|
||||
|
||||
impl HandleCustomValue {
|
||||
pub fn into_value(self, span: Span) -> Value {
|
||||
Value::custom(Box::new(self), span)
|
||||
}
|
||||
}
|
||||
|
||||
#[typetag::serde]
|
||||
impl CustomValue for HandleCustomValue {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
self.clone().into_value(span)
|
||||
}
|
||||
|
||||
fn type_name(&self) -> String {
|
||||
"HandleCustomValue".into()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
Err(LabeledError::new("Unsupported operation")
|
||||
.with_label("can't call to_base_value() directly on this", span)
|
||||
.with_help("HandleCustomValue uses custom_value_to_base_value() on the plugin instead")
|
||||
.into())
|
||||
}
|
||||
|
||||
fn notify_plugin_on_drop(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
61
crates/nu_plugin_custom_values/src/handle_get.rs
Normal file
61
crates/nu_plugin_custom_values/src/handle_get.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
|
||||
use nu_protocol::{LabeledError, ShellError, Signature, Type, Value};
|
||||
|
||||
use crate::{handle_custom_value::HandleCustomValue, CustomValuePlugin};
|
||||
|
||||
pub struct HandleGet;
|
||||
|
||||
impl SimplePluginCommand for HandleGet {
|
||||
type Plugin = CustomValuePlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"custom-value handle get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(Type::Custom("HandleCustomValue".into()), Type::Any)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get a value previously stored in a handle"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
plugin: &Self::Plugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
) -> Result<Value, LabeledError> {
|
||||
if let Some(handle) = input
|
||||
.as_custom_value()?
|
||||
.as_any()
|
||||
.downcast_ref::<HandleCustomValue>()
|
||||
{
|
||||
// Find the handle
|
||||
let value = plugin
|
||||
.handles
|
||||
.lock()
|
||||
.map_err(|err| LabeledError::new(err.to_string()))?
|
||||
.get(&handle.0)
|
||||
.cloned();
|
||||
|
||||
if let Some(value) = value {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(LabeledError::new("Handle expired")
|
||||
.with_label("this handle is no longer valid", input.span())
|
||||
.with_help("the plugin may have exited, or there was a bug"))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::UnsupportedInput {
|
||||
msg: "requires HandleCustomValue".into(),
|
||||
input: format!("got {}", input.get_type()),
|
||||
msg_span: call.head,
|
||||
input_span: input.span(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
47
crates/nu_plugin_custom_values/src/handle_make.rs
Normal file
47
crates/nu_plugin_custom_values/src/handle_make.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::sync::atomic;
|
||||
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
|
||||
use nu_protocol::{LabeledError, Signature, Type, Value};
|
||||
|
||||
use crate::{handle_custom_value::HandleCustomValue, CustomValuePlugin};
|
||||
|
||||
pub struct HandleMake;
|
||||
|
||||
impl SimplePluginCommand for HandleMake {
|
||||
type Plugin = CustomValuePlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"custom-value handle make"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(Type::Any, Type::Custom("HandleCustomValue".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Store a value in plugin memory and return a handle to it"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
plugin: &Self::Plugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
) -> Result<Value, LabeledError> {
|
||||
// Generate an id and store in the plugin.
|
||||
let new_id = plugin.counter.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
|
||||
plugin
|
||||
.handles
|
||||
.lock()
|
||||
.map_err(|err| LabeledError::new(err.to_string()))?
|
||||
.insert(new_id, input.clone());
|
||||
|
||||
Ok(Value::custom(
|
||||
Box::new(HandleCustomValue(new_id)),
|
||||
call.head,
|
||||
))
|
||||
}
|
||||
}
|
@ -1,22 +1,43 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{atomic::AtomicU64, Mutex},
|
||||
};
|
||||
|
||||
use handle_custom_value::HandleCustomValue;
|
||||
use nu_plugin::{serve_plugin, EngineInterface, MsgPackSerializer, Plugin, PluginCommand};
|
||||
|
||||
mod cool_custom_value;
|
||||
mod handle_custom_value;
|
||||
mod second_custom_value;
|
||||
|
||||
mod drop_check;
|
||||
mod generate;
|
||||
mod generate2;
|
||||
mod handle_get;
|
||||
mod handle_make;
|
||||
mod update;
|
||||
mod update_arg;
|
||||
|
||||
use drop_check::{DropCheck, DropCheckValue};
|
||||
use generate::Generate;
|
||||
use generate2::Generate2;
|
||||
use nu_protocol::{CustomValue, LabeledError};
|
||||
use handle_get::HandleGet;
|
||||
use handle_make::HandleMake;
|
||||
use nu_protocol::{CustomValue, LabeledError, Spanned, Value};
|
||||
use update::Update;
|
||||
use update_arg::UpdateArg;
|
||||
|
||||
pub struct CustomValuePlugin;
|
||||
#[derive(Default)]
|
||||
pub struct CustomValuePlugin {
|
||||
counter: AtomicU64,
|
||||
handles: Mutex<BTreeMap<u64, Value>>,
|
||||
}
|
||||
|
||||
impl CustomValuePlugin {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for CustomValuePlugin {
|
||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
@ -26,22 +47,54 @@ impl Plugin for CustomValuePlugin {
|
||||
Box::new(Update),
|
||||
Box::new(UpdateArg),
|
||||
Box::new(DropCheck),
|
||||
Box::new(HandleGet),
|
||||
Box::new(HandleMake),
|
||||
]
|
||||
}
|
||||
|
||||
fn custom_value_to_base_value(
|
||||
&self,
|
||||
_engine: &EngineInterface,
|
||||
custom_value: Spanned<Box<dyn CustomValue>>,
|
||||
) -> Result<Value, LabeledError> {
|
||||
// HandleCustomValue depends on the plugin state to get.
|
||||
if let Some(handle) = custom_value
|
||||
.item
|
||||
.as_any()
|
||||
.downcast_ref::<HandleCustomValue>()
|
||||
{
|
||||
Ok(self
|
||||
.handles
|
||||
.lock()
|
||||
.map_err(|err| LabeledError::new(err.to_string()))?
|
||||
.get(&handle.0)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::nothing(custom_value.span)))
|
||||
} else {
|
||||
custom_value
|
||||
.item
|
||||
.to_base_value(custom_value.span)
|
||||
.map_err(|err| err.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_value_dropped(
|
||||
&self,
|
||||
_engine: &EngineInterface,
|
||||
custom_value: Box<dyn CustomValue>,
|
||||
) -> Result<(), LabeledError> {
|
||||
// This is how we implement our drop behavior for DropCheck.
|
||||
// This is how we implement our drop behavior.
|
||||
if let Some(drop_check) = custom_value.as_any().downcast_ref::<DropCheckValue>() {
|
||||
drop_check.notify();
|
||||
} else if let Some(handle) = custom_value.as_any().downcast_ref::<HandleCustomValue>() {
|
||||
if let Ok(mut handles) = self.handles.lock() {
|
||||
handles.remove(&handle.0);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&CustomValuePlugin, MsgPackSerializer {})
|
||||
serve_plugin(&CustomValuePlugin::default(), MsgPackSerializer {})
|
||||
}
|
||||
|
@ -74,4 +74,8 @@ impl CustomValue for SecondCustomValue {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,6 @@ impl SimplePluginCommand for Update {
|
||||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("custom_values", crate::CustomValuePlugin.into())?
|
||||
PluginTest::new("custom_values", crate::CustomValuePlugin::new().into())?
|
||||
.test_command_examples(&Update)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_exam
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_example"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_example"
|
||||
@ -15,9 +15,9 @@ bench = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2", features = ["plugin"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.0" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.2" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.2" }
|
||||
|
@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_form
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2", features = ["plugin"] }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
eml-parser = "0.1"
|
||||
@ -18,4 +18,4 @@ ical = "0.11"
|
||||
rust-ini = "0.20.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.0" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.92.2" }
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_gsta
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@ -16,7 +16,7 @@ name = "nu_plugin_gstat"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
git2 = "0.18"
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_inc"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@ -16,7 +16,7 @@ name = "nu_plugin_inc"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2", features = ["plugin"] }
|
||||
|
||||
semver = "1.0"
|
||||
|
@ -27,7 +27,7 @@ import sys
|
||||
import json
|
||||
|
||||
|
||||
NUSHELL_VERSION = "0.91.1"
|
||||
NUSHELL_VERSION = "0.92.2"
|
||||
|
||||
|
||||
def signatures():
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_quer
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_query"
|
||||
version = "0.92.0"
|
||||
version = "0.92.2"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@ -16,8 +16,8 @@ name = "nu_plugin_query"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.0" }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.92.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.92.2" }
|
||||
|
||||
gjson = "0.8"
|
||||
scraper = { default-features = false, version = "0.19" }
|
||||
|
@ -16,4 +16,4 @@ profile = "default"
|
||||
# use in nushell, we may opt to use the bleeding edge stable version of rust.
|
||||
# I believe rust is on a 6 week release cycle and nushell is on a 4 week release cycle.
|
||||
# So, every two nushell releases, this version number should be bumped by one.
|
||||
channel = "1.75.0"
|
||||
channel = "1.77.2"
|
||||
|
@ -335,7 +335,7 @@ fn const_captures_in_closures_work() {
|
||||
assert_eq!(actual.out, "hello world");
|
||||
}
|
||||
|
||||
#[ignore = "TODO: Need to fix `overlay hide` to hide the constants brough by `overlay use`"]
|
||||
#[ignore = "TODO: Need to fix `overlay hide` to hide the constants brought by `overlay use`"]
|
||||
#[test]
|
||||
fn complex_const_overlay_use_hide() {
|
||||
let inp = &[MODULE_SETUP, "overlay use spam", "$X"];
|
||||
|
@ -551,3 +551,16 @@ fn err_hook_parse_error() {
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_change_overlay() {
|
||||
let inp = &[
|
||||
"module test { export-env { $env.BAR = 2 } }",
|
||||
&env_change_hook_code("FOO", "'overlay use test'"),
|
||||
"$env.FOO = 1",
|
||||
"$env.BAR",
|
||||
];
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
assert_eq!(actual_repl.out, "2");
|
||||
}
|
||||
|
@ -181,6 +181,20 @@ fn drop_check_custom_value_prints_message_on_drop() {
|
||||
assert!(actual.status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_make_then_get_success() {
|
||||
// The drop notification must wait until the `handle get` call has finished in order for this
|
||||
// to succeed
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: "tests",
|
||||
plugin: ("nu_plugin_custom_values"),
|
||||
"42 | custom-value handle make | custom-value handle get"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "42");
|
||||
assert!(actual.status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_value_in_example_is_rendered() {
|
||||
let actual = nu_with_plugins!(
|
||||
|
@ -1,10 +1,12 @@
|
||||
[files]
|
||||
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt"]
|
||||
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt", "tests/fixtures/formats/*"]
|
||||
|
||||
[default.extend-words]
|
||||
# Ignore false-positives
|
||||
nd = "nd"
|
||||
pn = "pn"
|
||||
fo = "fo"
|
||||
ful = "ful"
|
||||
ons = "ons"
|
||||
ba = "ba"
|
||||
Plasticos = "Plasticos"
|
||||
|
Reference in New Issue
Block a user