Compare commits

...

14 Commits

Author SHA1 Message Date
2a08a18b26 Bump version to 0.92.2 (#12402) 2024-04-10 23:15:51 +02:00
d5aad7a4ef Tweak release workflow after 0.92.1 lessons (#12401)
Encountered repeated build failure that vanished after clearing the
existing build caches.

- Don't `fail-fast` (this is highly annoying, if a severe problem is
detected, there is still the possibility to intervene manually)
- Don't cache the build process, we do it rarely so any potential
speed-up is offset by the uncertainty if this affects the artefact
2024-04-10 23:00:59 +02:00
5bc21fbb0a Bump our Rust version to stable
This was prompted by CVE-2024-24576

- https://nvd.nist.gov/vuln/detail/CVE-2024-24576
- https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html
- https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/

Affected is launching commands on Windows with arbitrary arguments,
which is the case for Nushell's external invocation on Windows

Rust has fixed this quoting vulnerability in 1.77.2 (latest stable at
time of commit)

We will thus use this version for our builds and recommend all our
packaging/distribution maintainers to use this version of Rust when
building Nushell.
2024-04-10 22:53:07 +02:00
f136e0601d Update MSRV following rust-toolchain.toml (#12455)
Also update the `rust-version` in `Cargo.toml` following the update to
`rust-toolchain.toml` in #12258

Added a CI check to verify any future PRs trying to update one will also
have to update the other. (using `std-lib-and-python-virtualenv` job as
this already includes a fresh `nu` binary for a little toml munching
script)
2024-04-10 22:51:03 +02:00
c00a05a762 Bump version to 0.92.1 (#12380) 2024-04-04 16:18:54 +02:00
51aa66fef7 Fix #12391: mkdir uses process startup directory instead of current script directory (#12394)
# Description

This fixes #12391.

nushell/nushell@87c5f6e455 accidentally introduced a bug where the path
was not being properly
expanded according to the cwd. This makes both 'touch' and 'mkdir' use
globs just like the rest of
the commands to preserve tilde behavior while still expanding the paths
properly.

This doesn't actually expand the globs. Should it?

# User-Facing Changes

- Restore behavior of `mkdir`, `touch`
- Help text now says they can take globs, but they won't actually expand
them, maybe this should be changed

# Tests + Formatting

Regression tests added.


# After Submitting

This is severe enough and should be included in the point release.
2024-04-04 14:23:10 +02:00
25b90744b7 fix simple typo in commandline_.rs (#12387)
Fix typo in crates/nu-cli/src/commands/commandline/commandline_.rs
`Replaceing` -> `Replacing`
2024-04-04 09:59:52 +02:00
e810995cf8 Bump crate-ci/typos and fix typos (#12381)
Supersede #12376
2024-04-04 09:59:21 +02:00
f0a073b397 prevent parser from parsing variables as units (#12378)
# Description

Resolves #11274.

```
~/CodingProjects/nushell> let day = 2; echo 0..<$day
╭───┬───╮
│ 0 │ 0 │
│ 1 │ 1 │
╰───┴───╯
~/CodingProjects/nushell> let kb = "jan"; echo 0..$kb 
Error: nu:🐚:type_mismatch

  × Type mismatch during operation.
   ╭─[entry #1:1:22]
 1 │ let kb = "jan"; echo 0..$kb
   ·                      ┬─┬─┬─
   ·                      │ │ ╰── string
   ·                      │ ╰── type mismatch for operator
   ·                      ╰── int
   ╰────
```


# Tests + Formatting

Relevant test added 🆙 

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-04-04 09:55:14 +02:00
cd00a489af Fix hooks on 0.92.0 (#12383)
# Description
Fixes #12382, where overlay changes from hooks were not preserved into
the global state. This was due to creating child stacks for hooks, when
the global stack should have been used instead.
2024-04-04 09:25:54 +02:00
cbf7feef22 Make drop notification timing for plugin custom values more consistent (#12341)
# Description
This keeps plugin custom values that have requested drop notification
around during the lifetime of a plugin call / stream by sending them to
a channel that gets persisted during the lifetime of the call.

Before this change, it was very likely that the drop notification would
be sent before the plugin ever had a chance to handle the value it
received.

Tests have been added to make sure this works - see the `custom_values`
plugin.

cc @ayax79 

# User-Facing Changes
This is basically just a bugfix, just a slightly big one.

However, I did add an `as_mut_any()` function for custom values, to
avoid having to clone them. This is a breaking change.
2024-04-04 09:13:25 +02:00
a6afc89338 Make default config conservative about clipboard (#12385)
Some platforms don't support the `system-clipboard` feature, notably
termux on android.
The default config currently contained references to `reedline` events
that are only available with the feature enabled (#12179). This thus
broke the out of the box config for those users.

For now be more defensive about this and only enable default events. Add
the alternative as commented out code you can quickly enable.

## Tested with:

```
cargo run --no-default-features --features default-no-clipboard -- --config crates/nu-utils/src/sample_config/default_config.nu
```
2024-04-04 09:10:41 +02:00
c7b9183e47 bump python plugin nushell version (#12367)
# Description

This PR bumps the python plugin to keep it in sync with the nushell
version.
2024-04-03 15:33:37 +02:00
b6c537d782 Add missing crate description to nu-plugin-test-support (#12368) 2024-04-03 15:33:18 +02:00
89 changed files with 1489 additions and 663 deletions

12
.github/workflows/check-msrv.nu vendored Normal file
View 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
}

View File

@ -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:

View File

@ -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

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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"] }

View File

@ -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![],

View File

@ -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)
}
///

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.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 }

View File

@ -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" }

View File

@ -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'))

View File

@ -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,

View File

@ -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,

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.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" }

View File

@ -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 }

View File

@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.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" }

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.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 }

View File

@ -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,

View File

@ -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}"),

View File

@ -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")?;

View File

@ -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 {

View File

@ -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());
})
}

View File

@ -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() {

View File

@ -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 = []

View File

@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
edition = "2021"
license = "MIT"
name = "nu-explore"
version = "0.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 }

View File

@ -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 = """

View File

@ -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"

View File

@ -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.

View File

@ -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"

View File

@ -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 }

View File

@ -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,

View File

@ -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)]

View File

@ -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]

View File

@ -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"

View File

@ -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()),

View File

@ -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()

View File

@ -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"

View File

@ -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)]

View File

@ -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))
}

View File

@ -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, _) => {

View File

@ -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

View File

@ -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,
&current_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>,

View File

@ -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:#?}",
);
}
}
}

View File

@ -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,

View File

@ -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`,

View File

@ -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);

View File

@ -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 {

View File

@ -1,3 +1,5 @@
mod mutable_cow;
mod with_custom_values_in;
pub(crate) use mutable_cow::*;
pub use with_custom_values_in::*;

View 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");
}

View File

@ -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

View File

@ -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]

View File

@ -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(

View File

@ -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).
///

View File

@ -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`].

View File

@ -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,

View File

@ -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"

View File

@ -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"

View File

@ -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" }

View File

@ -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 }

View File

@ -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 }

View File

@ -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]]

View File

@ -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 }
}
]
}

View File

@ -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 }) {

View File

@ -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" }

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View 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
}
}

View 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())
}
}
}

View 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,
))
}
}

View File

@ -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 {})
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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" }

View File

@ -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" }

View File

@ -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"

View File

@ -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"

View File

@ -27,7 +27,7 @@ import sys
import json
NUSHELL_VERSION = "0.91.1"
NUSHELL_VERSION = "0.92.2"
def signatures():

View File

@ -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" }

View File

@ -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"

View File

@ -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"];

View File

@ -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");
}

View File

@ -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!(

View File

@ -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"