From 532c0023c24ef44fbad303cf8e0e9a0e32d0e29e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 00:59:03 +0000 Subject: [PATCH 01/38] Bump crate-ci/typos from 1.22.4 to 1.22.7 (#13176) --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index f81f5c89a5..d33750e337 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.6 - name: Check spelling - uses: crate-ci/typos@v1.22.4 + uses: crate-ci/typos@v1.22.7 From 103f59be52eadcb8f478264487e1f4834ce027bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:09:25 +0000 Subject: [PATCH 02/38] Bump git2 from 0.18.3 to 0.19.0 (#13179) --- Cargo.lock | 8 ++++---- crates/nu_plugin_gstat/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b88af73c25..0db346b785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1687,9 +1687,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.5.0", "libc", @@ -2288,9 +2288,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index 01e17fdb54..c356f3d7d3 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -19,4 +19,4 @@ bench = false nu-plugin = { path = "../nu-plugin", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -git2 = "0.18" +git2 = "0.19" From 12991cd36feedaac0ccb86be61e78a8ca4ffd261 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Tue, 18 Jun 2024 21:37:24 -0700 Subject: [PATCH 03/38] Change the error style during tests to `plain` (#13061) # Description This fixes issues with trying to run the tests with a terminal that is small enough to cause errors to wrap around, or in cases where the test environment might produce strings that are reasonably expected to wrap around anyway. "Fancy" errors are too fancy for tests to work predictably :wink: cc @abusch # User-Facing Changes - Added `--error-style` option for use with `--commands` (like `--table-mode`) # Tests + Formatting Surprisingly, all of the tests pass, including in small windows! I only had to make one change to a test for `error make` which was looking for the box drawing characters miette uses to determine whether the span label was showing up - but the plain error style output is even better and easier to match on, so this test is actually more specific now. --- benches/benchmarks.rs | 6 ++-- crates/nu-cli/src/eval_cmds.rs | 34 +++++++++++++++++-- crates/nu-cli/src/lib.rs | 2 +- .../nu-command/tests/commands/error_make.rs | 5 +-- crates/nu-test-support/src/macros.rs | 4 +++ src/command.rs | 16 +++++++-- src/run.rs | 9 +++-- 7 files changed, 62 insertions(+), 14 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 84552daef9..7a23715c8d 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) { &mut engine, &mut stack, PipelineData::empty(), - None, - false, + Default::default(), ) .unwrap(); @@ -90,8 +89,7 @@ fn bench_command( &mut engine, &mut stack, PipelineData::empty(), - None, - false, + Default::default(), ) .unwrap(), ); diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 8fa3bf30e5..13141f6174 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -8,15 +8,45 @@ use nu_protocol::{ }; use std::sync::Arc; +#[derive(Default)] +pub struct EvaluateCommandsOpts { + pub table_mode: Option, + pub error_style: Option, + pub no_newline: bool, +} + /// Run a command (or commands) given to us by the user pub fn evaluate_commands( commands: &Spanned, engine_state: &mut EngineState, stack: &mut Stack, input: PipelineData, - table_mode: Option, - no_newline: bool, + opts: EvaluateCommandsOpts, ) -> Result<(), ShellError> { + let EvaluateCommandsOpts { + table_mode, + error_style, + no_newline, + } = opts; + + // Handle the configured error style early + if let Some(e_style) = error_style { + match e_style.coerce_str()?.parse() { + Ok(e_style) => { + Arc::make_mut(&mut engine_state.config).error_style = e_style; + } + Err(err) => { + return Err(ShellError::GenericError { + error: "Invalid value for `--error-style`".into(), + msg: err.into(), + span: Some(e_style.span()), + help: None, + inner: vec![], + }); + } + } + } + // Translate environment variables from Strings to Values convert_env_values(engine_state, stack)?; diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index c4342dc3a0..6f151adad1 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -17,7 +17,7 @@ mod validation; pub use commands::add_cli_context; pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind}; pub use config_files::eval_config_contents; -pub use eval_cmds::evaluate_commands; +pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts}; pub use eval_file::evaluate_file; pub use menus::NuHelpCompleter; pub use nu_cmd_base::util::get_init_cwd; diff --git a/crates/nu-command/tests/commands/error_make.rs b/crates/nu-command/tests/commands/error_make.rs index 0c6908d2aa..f5714c88ac 100644 --- a/crates/nu-command/tests/commands/error_make.rs +++ b/crates/nu-command/tests/commands/error_make.rs @@ -4,8 +4,9 @@ use nu_test_support::nu; fn error_label_works() { let actual = nu!("error make {msg:foo label:{text:unseen}}"); - assert!(actual.err.contains("unseen")); - assert!(actual.err.contains("╰──")); + assert!(actual + .err + .contains("label at line 1, columns 1 to 10: unseen")); } #[test] diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index c18dd5ac81..51d743e1a4 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -283,6 +283,8 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O if !with_std { command.arg("--no-std-lib"); } + // Use plain errors to help make error text matching more consistent + command.args(["--error-style", "plain"]); command .arg(format!("-c {}", escape_quote_string(&commands))) .stdout(Stdio::piped()) @@ -369,6 +371,8 @@ where .envs(envs) .arg("--commands") .arg(command) + // Use plain errors to help make error text matching more consistent + .args(["--error-style", "plain"]) .arg("--config") .arg(temp_config_file) .arg("--env-config") diff --git a/src/command.rs b/src/command.rs index 15da0e3e47..5ba8a60996 100644 --- a/src/command.rs +++ b/src/command.rs @@ -29,8 +29,10 @@ pub(crate) fn gather_commandline_args() -> (Vec, String, Vec) { } let flag_value = match arg.as_ref() { - "--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" | "--config" - | "--env-config" | "-I" | "ide-ast" => args.next().map(|a| escape_quote_string(&a)), + "--commands" | "-c" | "--table-mode" | "-m" | "--error-style" | "-e" | "--execute" + | "--config" | "--env-config" | "-I" | "ide-ast" => { + args.next().map(|a| escape_quote_string(&a)) + } #[cfg(feature = "plugin")] "--plugin-config" => args.next().map(|a| escape_quote_string(&a)), "--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin" @@ -102,6 +104,8 @@ pub(crate) fn parse_commandline_args( let execute = call.get_flag_expr("execute"); let table_mode: Option = call.get_flag(engine_state, &mut stack, "table-mode")?; + let error_style: Option = + call.get_flag(engine_state, &mut stack, "error-style")?; let no_newline = call.get_named_arg("no-newline"); // ide flags @@ -245,6 +249,7 @@ pub(crate) fn parse_commandline_args( ide_check, ide_ast, table_mode, + error_style, no_newline, }); } @@ -278,6 +283,7 @@ pub(crate) struct NushellCliArgs { pub(crate) log_exclude: Option>>, pub(crate) execute: Option>, pub(crate) table_mode: Option, + pub(crate) error_style: Option, pub(crate) no_newline: Option>, pub(crate) include_path: Option>, pub(crate) lsp: bool, @@ -325,6 +331,12 @@ impl Command for Nu { "the table mode to use. rounded is default.", Some('m'), ) + .named( + "error-style", + SyntaxShape::String, + "the error style to use (fancy or plain). default: fancy", + None, + ) .switch("no-newline", "print the result for --commands(-c) without a newline", None) .switch( "no-config-file", diff --git a/src/run.rs b/src/run.rs index 2996bc76f8..db779b0058 100644 --- a/src/run.rs +++ b/src/run.rs @@ -7,7 +7,7 @@ use crate::{ use log::trace; #[cfg(feature = "plugin")] use nu_cli::read_plugin_file; -use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl}; +use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl, EvaluateCommandsOpts}; use nu_protocol::{ engine::{EngineState, Stack}, report_error_new, PipelineData, Spanned, @@ -114,8 +114,11 @@ pub(crate) fn run_commands( engine_state, &mut stack, input, - parsed_nu_cli_args.table_mode, - parsed_nu_cli_args.no_newline.is_some(), + EvaluateCommandsOpts { + table_mode: parsed_nu_cli_args.table_mode, + error_style: parsed_nu_cli_args.error_style, + no_newline: parsed_nu_cli_args.no_newline.is_some(), + }, ) { report_error_new(engine_state, &err); std::process::exit(1); From 44aa0a2de41295851a035fde3c3d5b7a2e3e7030 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:12:25 -0400 Subject: [PATCH 04/38] Table help rendering (#13182) # Description Mostly fixes #13149 with much of the credit to @fdncred. This PR runs `table --expand` against `help` example results. This is essentially the same fix that #13146 was for `std help`. It also changes the shape of the result for the `table --expand` example, as it was hardcoded wrong. ~Still needed is a fix for the `table --collapse` example.~ Note that this is also still a bug in `std help` that I didn't noticed before. # User-Facing Changes Certain tables are now rendered correctly in the help examples for: * `table` * `zip` * `flatten` * And almost certainly others # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-command/src/viewers/table.rs | 10 ++++++++-- crates/nu-engine/src/documentation.rs | 26 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2fe9319821..075a4e31c4 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -170,7 +170,10 @@ impl Command for Table { }), Value::test_record(record! { "a" => Value::test_int(3), - "b" => Value::test_int(4), + "b" => Value::test_list(vec![ + Value::test_int(4), + Value::test_int(4), + ]) }), ])), }, @@ -184,7 +187,10 @@ impl Command for Table { }), Value::test_record(record! { "a" => Value::test_int(3), - "b" => Value::test_int(4), + "b" => Value::test_list(vec![ + Value::test_int(4), + Value::test_int(4), + ]) }), ])), }, diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 94cef3298e..a7d4950036 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -3,7 +3,7 @@ use nu_protocol::{ ast::{Argument, Call, Expr, Expression, RecordItem}, debugger::WithoutDebug, engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID}, - record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, + record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned, SyntaxShape, Type, Value, }; use std::{collections::HashMap, fmt::Write}; @@ -296,6 +296,28 @@ fn get_documentation( } if let Some(result) = &example.result { + let mut table_call = Call::new(Span::unknown()); + if example.example.ends_with("--collapse") { + // collapse the result + table_call.add_named(( + Spanned { + item: "collapse".to_string(), + span: Span::unknown(), + }, + None, + None, + )) + } else { + // expand the result + table_call.add_named(( + Spanned { + item: "expand".to_string(), + span: Span::unknown(), + }, + None, + None, + )) + } let table = engine_state .find_decl("table".as_bytes(), &[]) .and_then(|decl_id| { @@ -304,7 +326,7 @@ fn get_documentation( .run( engine_state, stack, - &Call::new(Span::new(0, 0)), + &table_call, PipelineData::Value(result.clone(), None), ) .ok() From bdc32345bd6767071c169ffbcd9ffc30ea935791 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 19 Jun 2024 21:00:03 -0700 Subject: [PATCH 05/38] Move most of the peculiar argument handling for external calls into the parser (#13089) # Description We've had a lot of different issues and PRs related to arg handling with externals since the rewrite of `run-external` in #12921: - #12950 - #12955 - #13000 - #13001 - #13021 - #13027 - #13028 - #13073 Many of these are caused by the argument handling of external calls and `run-external` being very special and involving the parser handing quoted strings over to `run-external` so that it knows whether to expand tildes and globs and so on. This is really unusual and also makes it harder to use `run-external`, and also harder to understand it (and probably is part of the reason why it was rewritten in the first place). This PR moves a lot more of that work over to the parser, so that by the time `run-external` gets it, it's dealing with much more normal Nushell values. In particular: - Unquoted strings are handled as globs with no expand - The unescaped-but-quoted handling of strings was removed, and the parser constructs normal looking strings instead, removing internal quotes so that `run-external` doesn't have to do it - Bare word interpolation is now supported and expansion is done in this case - Expressions typed as `Glob` containing `Expr::StringInterpolation` now produce `Value::Glob` instead, with the quoted status from the expr passed through so we know if it was a bare word - Bare word interpolation for values typed as `glob` now possible, but not implemented - Because expansion is now triggered by `Value::Glob(_, false)` instead of looking at the expr, externals now support glob types # User-Facing Changes - Bare word interpolation works for external command options, and otherwise embedded in other strings: ```nushell ^echo --foo=(2 + 2) # prints --foo=4 ^echo -foo=$"(2 + 2)" # prints -foo=4 ^echo foo="(2 + 2)" # prints (no interpolation!) foo=(2 + 2) ^echo foo,(2 + 2),bar # prints foo,4,bar ``` - Bare word interpolation expands for external command head/args: ```nushell let name = "exa" ~/.cargo/bin/($name) # this works, and expands the tilde ^$"~/.cargo/bin/($name)" # this doesn't expand the tilde ^echo ~/($name)/* # this glob is expanded ^echo $"~/($name)/*" # this isn't expanded ``` - Ndots are now supported for the head of an external command (`^.../foo` works) - Glob values are now supported for head/args of an external command, and expanded appropriately: ```nushell ^("~/.cargo/bin/exa" | into glob) # the tilde is expanded ^echo ("*.txt" | into glob) # this glob is expanded ``` - `run-external` now works more like any other command, without expecting a special call convention for its args: ```nushell run-external echo "'foo'" # before PR: 'foo' # after PR: foo run-external echo "*.txt" # before PR: (glob is expanded) # after PR: *.txt ``` # Tests + Formatting Lots of tests added and cleaned up. Some tests that weren't active on Windows changed to use `nu --testbin cococo` so that they can work. Added a test for Linux only to make sure tilde expansion of commands works, because changing `HOME` there causes `~` to reliably change. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] release notes: make sure to mention the new syntaxes that are supported --- crates/nu-cli/src/syntax_highlight.rs | 21 +- crates/nu-color-config/src/shape_color.rs | 1 + crates/nu-command/src/system/run_external.rs | 317 +++------- .../nu-command/tests/commands/run_external.rs | 145 +++-- crates/nu-parser/src/flatten.rs | 25 +- crates/nu-parser/src/parser.rs | 251 ++++++-- crates/nu-parser/tests/test_parser.rs | 564 +++++++++++++----- crates/nu-protocol/src/ast/expr.rs | 6 + crates/nu-protocol/src/ast/expression.rs | 4 +- crates/nu-protocol/src/debugger/profiler.rs | 1 + crates/nu-protocol/src/eval_base.rs | 9 + crates/nu-test-support/src/macros.rs | 6 + crates/nuon/src/from.rs | 6 + 13 files changed, 880 insertions(+), 476 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index e296943af6..41ef168390 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -138,6 +138,7 @@ impl Highlighter for NuHighlighter { FlatShape::Filepath => add_colored_token(&shape.1, next_token), FlatShape::Directory => add_colored_token(&shape.1, next_token), + FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token), FlatShape::GlobPattern => add_colored_token(&shape.1, next_token), FlatShape::Variable(_) | FlatShape::VarDecl(_) => { add_colored_token(&shape.1, next_token) @@ -452,15 +453,17 @@ fn find_matching_block_end_in_expr( } } - Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| { - find_matching_block_end_in_expr( - line, - working_set, - expr, - global_span_offset, - global_cursor_offset, - ) - }), + Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => { + exprs.iter().find_map(|expr| { + find_matching_block_end_in_expr( + line, + working_set, + expr, + global_span_offset, + global_cursor_offset, + ) + }) + } Expr::List(list) => { if expr_last == global_cursor_offset { diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 72cc955e0b..8da397e914 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -20,6 +20,7 @@ pub fn default_shape_color(shape: &str) -> Style { "shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_float" => Style::new().fg(Color::Purple).bold(), "shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(), + "shape_glob_interpolation" => Style::new().fg(Color::Cyan).bold(), "shape_globpattern" => Style::new().fg(Color::Cyan).bold(), "shape_int" => Style::new().fg(Color::Purple).bold(), "shape_internalcall" => Style::new().fg(Color::Cyan).bold(), diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index bc6152e7c8..104729a786 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,16 +1,15 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; +use nu_path::{dots::expand_ndots, expand_tilde}; use nu_protocol::{ - ast::{Expr, Expression}, - did_you_mean, - process::ChildProcess, - ByteStream, NuGlob, OutDest, + ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, }; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; use std::{ borrow::Cow, + ffi::{OsStr, OsString}, io::Write, path::{Path, PathBuf}, process::Stdio, @@ -33,8 +32,16 @@ impl Command for External { fn signature(&self) -> nu_protocol::Signature { Signature::build(self.name()) .input_output_types(vec![(Type::Any, Type::Any)]) - .required("command", SyntaxShape::String, "External command to run.") - .rest("args", SyntaxShape::Any, "Arguments for external command.") + .required( + "command", + SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]), + "External command to run.", + ) + .rest( + "args", + SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]), + "Arguments for external command.", + ) .category(Category::System) } @@ -47,42 +54,31 @@ impl Command for External { ) -> Result { let cwd = engine_state.cwd(Some(stack))?; - // Evaluate the command name in the same way the arguments are evaluated. Since this isn't - // a spread, it should return a one-element vec. - let name_expr = call - .positional_nth(0) - .ok_or_else(|| ShellError::MissingParameter { - param_name: "command".into(), - span: call.head, - })?; - let name = eval_argument(engine_state, stack, name_expr, false)? - .pop() - .expect("eval_argument returned zero-element vec") - .into_spanned(name_expr.span); + let name: Value = call.req(engine_state, stack, 0)?; + + let name_str: Cow = match &name { + Value::Glob { val, .. } => Cow::Borrowed(val), + Value::String { val, .. } => Cow::Borrowed(val), + _ => Cow::Owned(name.clone().coerce_into_string()?), + }; + + let expanded_name = match &name { + // Expand tilde and ndots on the name if it's a bare string / glob (#13000) + Value::Glob { no_expand, .. } if !*no_expand => expand_ndots(expand_tilde(&*name_str)), + _ => Path::new(&*name_str).to_owned(), + }; // Find the absolute path to the executable. On Windows, set the // executable to "cmd.exe" if it's is a CMD internal command. If the // command is not found, display a helpful error message. - let executable = if cfg!(windows) && is_cmd_internal_command(&name.item) { + let executable = if cfg!(windows) && is_cmd_internal_command(&name_str) { PathBuf::from("cmd.exe") } else { - // Expand tilde on the name if it's a bare string (#13000) - let expanded_name = if is_bare_string(name_expr) { - expand_tilde(&name.item) - } else { - name.item.clone() - }; - // Determine the PATH to be used and then use `which` to find it - though this has no // effect if it's an absolute path already let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = which(&expanded_name, &paths, &cwd) else { - return Err(command_not_found( - &name.item, - call.head, - engine_state, - stack, - )); + let Some(executable) = which(expanded_name, &paths, &cwd) else { + return Err(command_not_found(&name_str, call.head, engine_state, stack)); }; executable }; @@ -101,15 +97,15 @@ impl Command for External { // Configure args. let args = eval_arguments_from_call(engine_state, stack, call)?; #[cfg(windows)] - if is_cmd_internal_command(&name.item) { + if is_cmd_internal_command(&name_str) { use std::os::windows::process::CommandExt; // The /D flag disables execution of AutoRun commands from registry. // The /C flag followed by a command name instructs CMD to execute // that command and quit. - command.args(["/D", "/C", &name.item]); + command.args(["/D", "/C", &name_str]); for arg in &args { - command.raw_arg(escape_cmd_argument(arg)?.as_ref()); + command.raw_arg(escape_cmd_argument(arg)?); } } else { command.args(args.into_iter().map(|s| s.item)); @@ -217,76 +213,54 @@ impl Command for External { } } -/// Removes surrounding quotes from a string. Doesn't remove quotes from raw -/// strings. Returns the original string if it doesn't have matching quotes. -fn remove_quotes(s: &str) -> Cow<'_, str> { - let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"'); - let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\''); - let quoted_by_backticks = s.len() >= 2 && s.starts_with('`') && s.ends_with('`'); - if quoted_by_double_quotes { - Cow::Owned(s[1..s.len() - 1].to_string().replace(r#"\""#, "\"")) - } else if quoted_by_single_quotes || quoted_by_backticks { - Cow::Borrowed(&s[1..s.len() - 1]) - } else { - Cow::Borrowed(s) - } -} - /// Evaluate all arguments from a call, performing expansions when necessary. pub fn eval_arguments_from_call( engine_state: &EngineState, stack: &mut Stack, call: &Call, -) -> Result>, ShellError> { +) -> Result>, ShellError> { let ctrlc = &engine_state.ctrlc; let cwd = engine_state.cwd(Some(stack))?; - let mut args: Vec> = vec![]; + let mut args: Vec> = vec![]; for (expr, spread) in call.rest_iter(1) { - if is_bare_string(expr) { - // If `expr` is a bare string, perform tilde-expansion, - // glob-expansion, and inner-quotes-removal, in that order. - for arg in eval_argument(engine_state, stack, expr, spread)? { - let tilde_expanded = expand_tilde(&arg); - for glob_expanded in expand_glob(&tilde_expanded, &cwd, expr.span, ctrlc)? { - let inner_quotes_removed = remove_inner_quotes(&glob_expanded); - args.push(inner_quotes_removed.into_owned().into_spanned(expr.span)); + for arg in eval_argument(engine_state, stack, expr, spread)? { + match arg { + // Expand globs passed to run-external + Value::Glob { val, no_expand, .. } if !no_expand => args.extend( + expand_glob(&val, &cwd, expr.span, ctrlc)? + .into_iter() + .map(|s| s.into_spanned(expr.span)), + ), + other => { + args.push(OsString::from(coerce_into_string(other)?).into_spanned(expr.span)) } } - } else { - for arg in eval_argument(engine_state, stack, expr, spread)? { - args.push(arg.into_spanned(expr.span)); - } } } Ok(args) } -/// Evaluates an expression, coercing the values to strings. -/// -/// Note: The parser currently has a special hack that retains surrounding -/// quotes for string literals in `Expression`, so that we can decide whether -/// the expression is considered a bare string. The hack doesn't affect string -/// literals within lists or records. This function will remove the quotes -/// before evaluating the expression. +/// Custom `coerce_into_string()`, including globs, since those are often args to `run-external` +/// as well +fn coerce_into_string(val: Value) -> Result { + match val { + Value::Glob { val, .. } => Ok(val), + _ => val.coerce_into_string(), + } +} + +/// Evaluate an argument, returning more than one value if it was a list to be spread. fn eval_argument( engine_state: &EngineState, stack: &mut Stack, expr: &Expression, spread: bool, -) -> Result, ShellError> { - // Remove quotes from string literals. - let mut expr = expr.clone(); - if let Expr::String(s) = &expr.expr { - expr.expr = Expr::String(remove_quotes(s).into()); - } - +) -> Result, ShellError> { let eval = get_eval_expression(engine_state); - match eval(engine_state, stack, &expr)? { + match eval(engine_state, stack, expr)? { Value::List { vals, .. } => { if spread { - vals.into_iter() - .map(|val| val.coerce_into_string()) - .collect() + Ok(vals) } else { Err(ShellError::CannotPassListToExternal { arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(), @@ -298,31 +272,12 @@ fn eval_argument( if spread { Err(ShellError::CannotSpreadAsList { span: expr.span }) } else { - Ok(vec![value.coerce_into_string()?]) + Ok(vec![value]) } } } } -/// Returns whether an expression is considered a bare string. -/// -/// Bare strings are defined as string literals that are either unquoted or -/// quoted by backticks. Raw strings or string interpolations don't count. -fn is_bare_string(expr: &Expression) -> bool { - let Expr::String(s) = &expr.expr else { - return false; - }; - let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"'); - let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\''); - !quoted_by_double_quotes && !quoted_by_single_quotes -} - -/// Performs tilde expansion on `arg`. Returns the original string if `arg` -/// doesn't start with tilde. -fn expand_tilde(arg: &str) -> String { - nu_path::expand_tilde(arg).to_string_lossy().to_string() -} - /// Performs glob expansion on `arg`. If the expansion found no matches or the pattern /// is not a valid glob, then this returns the original string as the expansion result. /// @@ -333,19 +288,21 @@ fn expand_glob( cwd: &Path, span: Span, interrupt: &Option>, -) -> Result, ShellError> { +) -> Result, ShellError> { const GLOB_CHARS: &[char] = &['*', '?', '[']; - // Don't expand something that doesn't include the GLOB_CHARS + // For an argument that doesn't include the GLOB_CHARS, just do the `expand_tilde` + // and `expand_ndots` expansion if !arg.contains(GLOB_CHARS) { - return Ok(vec![arg.into()]); + let path = expand_ndots(expand_tilde(arg)); + return Ok(vec![path.into()]); } // We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct // dir let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span); if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) { - let mut result = vec![]; + let mut result: Vec = vec![]; for m in matches { if nu_utils::ctrl_c::was_pressed(interrupt) { @@ -353,7 +310,7 @@ fn expand_glob( } if let Ok(arg) = m { let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd); - result.push(arg.to_string_lossy().to_string()); + result.push(arg.into()); } else { result.push(arg.into()); } @@ -392,23 +349,6 @@ fn resolve_globbed_path_to_cwd_relative( } } -/// Transforms `--option="value"` into `--option=value`. `value` can be quoted -/// with double quotes, single quotes, or backticks. Only removes the outermost -/// pair of quotes after the equal sign. -fn remove_inner_quotes(arg: &str) -> Cow<'_, str> { - // Split `arg` on the first `=`. - let Some((option, value)) = arg.split_once('=') else { - return Cow::Borrowed(arg); - }; - // Check that `option` doesn't contain quotes. - if option.contains('"') || option.contains('\'') || option.contains('`') { - return Cow::Borrowed(arg); - } - // Remove the outermost pair of quotes from `value`. - let value = remove_quotes(value); - Cow::Owned(format!("{option}={value}")) -} - /// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is /// first rendered using the `table` command. /// @@ -577,7 +517,7 @@ pub fn command_not_found( /// Note: the `which.rs` crate always uses PATHEXT from the environment. As /// such, changing PATHEXT within Nushell doesn't work without updating the /// actual environment of the Nushell process. -pub fn which(name: &str, paths: &str, cwd: &Path) -> Option { +pub fn which(name: impl AsRef, paths: &str, cwd: &Path) -> Option { #[cfg(windows)] let paths = format!("{};{}", cwd.display(), paths); which::which_in(name, Some(paths), cwd).ok() @@ -593,17 +533,18 @@ fn is_cmd_internal_command(name: &str) -> bool { } /// Returns true if a string contains CMD special characters. -#[cfg(windows)] -fn has_cmd_special_character(s: &str) -> bool { - const SPECIAL_CHARS: &[char] = &['<', '>', '&', '|', '^']; - SPECIAL_CHARS.iter().any(|c| s.contains(*c)) +fn has_cmd_special_character(s: impl AsRef<[u8]>) -> bool { + s.as_ref() + .iter() + .any(|b| matches!(b, b'<' | b'>' | b'&' | b'|' | b'^')) } /// Escape an argument for CMD internal commands. The result can be safely passed to `raw_arg()`. -#[cfg(windows)] -fn escape_cmd_argument(arg: &Spanned) -> Result, ShellError> { +#[cfg_attr(not(windows), allow(dead_code))] +fn escape_cmd_argument(arg: &Spanned) -> Result, ShellError> { let Spanned { item: arg, span } = arg; - if arg.contains(['\r', '\n', '%']) { + let bytes = arg.as_encoded_bytes(); + if bytes.iter().any(|b| matches!(b, b'\r' | b'\n' | b'%')) { // \r and \n trunacte the rest of the arguments and % can expand environment variables Err(ShellError::ExternalCommand { label: @@ -612,12 +553,12 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, ShellError help: "some characters currently cannot be securely escaped".into(), span: *span, }) - } else if arg.contains('"') { + } else if bytes.contains(&b'"') { // If `arg` is already quoted by double quotes, confirm there's no // embedded double quotes, then leave it as is. - if arg.chars().filter(|c| *c == '"').count() == 2 - && arg.starts_with('"') - && arg.ends_with('"') + if bytes.iter().filter(|b| **b == b'"').count() == 2 + && bytes.starts_with(b"\"") + && bytes.ends_with(b"\"") { Ok(Cow::Borrowed(arg)) } else { @@ -628,9 +569,13 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, ShellError span: *span, }) } - } else if arg.contains(' ') || has_cmd_special_character(arg) { + } else if bytes.contains(&b' ') || has_cmd_special_character(bytes) { // If `arg` contains space or special characters, quote the entire argument by double quotes. - Ok(Cow::Owned(format!("\"{arg}\""))) + let mut new_str = OsString::new(); + new_str.push("\""); + new_str.push(arg); + new_str.push("\""); + Ok(Cow::Owned(new_str)) } else { // FIXME?: what if `arg.is_empty()`? Ok(Cow::Borrowed(arg)) @@ -640,64 +585,8 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, ShellError #[cfg(test)] mod test { use super::*; - use nu_protocol::ast::ListItem; use nu_test_support::{fs::Stub, playground::Playground}; - #[test] - fn test_remove_quotes() { - assert_eq!(remove_quotes(r#""#), r#""#); - assert_eq!(remove_quotes(r#"'"#), r#"'"#); - assert_eq!(remove_quotes(r#"''"#), r#""#); - assert_eq!(remove_quotes(r#""foo""#), r#"foo"#); - assert_eq!(remove_quotes(r#"`foo '"' bar`"#), r#"foo '"' bar"#); - assert_eq!(remove_quotes(r#"'foo' bar"#), r#"'foo' bar"#); - assert_eq!(remove_quotes(r#"r#'foo'#"#), r#"r#'foo'#"#); - assert_eq!(remove_quotes(r#""foo\" bar""#), r#"foo" bar"#); - } - - #[test] - fn test_eval_argument() { - fn expression(expr: Expr) -> Expression { - Expression::new_unknown(expr, Span::unknown(), Type::Any) - } - - fn eval(expr: Expr, spread: bool) -> Result, ShellError> { - let engine_state = EngineState::new(); - let mut stack = Stack::new(); - eval_argument(&engine_state, &mut stack, &expression(expr), spread) - } - - let actual = eval(Expr::String("".into()), false).unwrap(); - let expected = &[""]; - assert_eq!(actual, expected); - - let actual = eval(Expr::String("'foo'".into()), false).unwrap(); - let expected = &["foo"]; - assert_eq!(actual, expected); - - let actual = eval(Expr::RawString("'foo'".into()), false).unwrap(); - let expected = &["'foo'"]; - assert_eq!(actual, expected); - - let actual = eval(Expr::List(vec![]), true).unwrap(); - let expected: &[&str] = &[]; - assert_eq!(actual, expected); - - let actual = eval( - Expr::List(vec![ - ListItem::Item(expression(Expr::String("'foo'".into()))), - ListItem::Item(expression(Expr::String("bar".into()))), - ]), - true, - ) - .unwrap(); - let expected = &["'foo'", "bar"]; - assert_eq!(actual, expected); - - eval(Expr::String("".into()), true).unwrap_err(); - eval(Expr::List(vec![]), false).unwrap_err(); - } - #[test] fn test_expand_glob() { Playground::setup("test_expand_glob", |dirs, play| { @@ -721,46 +610,20 @@ mod test { assert_eq!(actual, expected); let actual = expand_glob("./a.txt", cwd, Span::unknown(), &None).unwrap(); - let expected = &["./a.txt"]; + let expected: Vec = vec![Path::new(".").join("a.txt").into()]; assert_eq!(actual, expected); let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); let expected = &["[*.txt"]; assert_eq!(actual, expected); + + let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &None).unwrap(); + let home = dirs_next::home_dir().expect("failed to get home dir"); + let expected: Vec = vec![home.join("foo.txt").into()]; + assert_eq!(actual, expected); }) } - #[test] - fn test_remove_inner_quotes() { - let actual = remove_inner_quotes(r#"--option=value"#); - let expected = r#"--option=value"#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"--option="value""#); - let expected = r#"--option=value"#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"--option='value'"#); - let expected = r#"--option=value"#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"--option "value""#); - let expected = r#"--option "value""#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"-option="value""#); - let expected = r#"-option=value"#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"option="value""#); - let expected = r#"option=value"#; - assert_eq!(actual, expected); - - let actual = remove_inner_quotes(r#"option="v\"value""#); - let expected = r#"option=v"value"#; - assert_eq!(actual, expected); - } - #[test] fn test_write_pipeline_data() { let engine_state = EngineState::new(); diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 14428edcbe..daa5abb25b 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -1,4 +1,3 @@ -#[cfg(not(windows))] use nu_test_support::fs::Stub::EmptyFile; use nu_test_support::playground::Playground; use nu_test_support::{nu, pipeline}; @@ -17,7 +16,6 @@ fn better_empty_redirection() { assert!(!actual.out.contains('2')); } -#[cfg(not(windows))] #[test] fn explicit_glob() { Playground::setup("external with explicit glob", |dirs, sandbox| { @@ -30,15 +28,15 @@ fn explicit_glob() { let actual = nu!( cwd: dirs.test(), pipeline( r#" - ^ls | glob '*.txt' | length + ^nu --testbin cococo ('*.txt' | into glob) "# )); - assert_eq!(actual.out, "2"); + assert!(actual.out.contains("D&D_volume_1.txt")); + assert!(actual.out.contains("D&D_volume_2.txt")); }) } -#[cfg(not(windows))] #[test] fn bare_word_expand_path_glob() { Playground::setup("bare word should do the expansion", |dirs, sandbox| { @@ -51,7 +49,7 @@ fn bare_word_expand_path_glob() { let actual = nu!( cwd: dirs.test(), pipeline( " - ^ls *.txt + ^nu --testbin cococo *.txt " )); @@ -60,7 +58,6 @@ fn bare_word_expand_path_glob() { }) } -#[cfg(not(windows))] #[test] fn backtick_expand_path_glob() { Playground::setup("backtick should do the expansion", |dirs, sandbox| { @@ -73,7 +70,7 @@ fn backtick_expand_path_glob() { let actual = nu!( cwd: dirs.test(), pipeline( r#" - ^ls `*.txt` + ^nu --testbin cococo `*.txt` "# )); @@ -82,7 +79,6 @@ fn backtick_expand_path_glob() { }) } -#[cfg(not(windows))] #[test] fn single_quote_does_not_expand_path_glob() { Playground::setup("single quote do not run the expansion", |dirs, sandbox| { @@ -95,15 +91,14 @@ fn single_quote_does_not_expand_path_glob() { let actual = nu!( cwd: dirs.test(), pipeline( r#" - ^ls '*.txt' + ^nu --testbin cococo '*.txt' "# )); - assert!(actual.err.contains("No such file or directory")); + assert_eq!(actual.out, "*.txt"); }) } -#[cfg(not(windows))] #[test] fn double_quote_does_not_expand_path_glob() { Playground::setup("double quote do not run the expansion", |dirs, sandbox| { @@ -116,22 +111,21 @@ fn double_quote_does_not_expand_path_glob() { let actual = nu!( cwd: dirs.test(), pipeline( r#" - ^ls "*.txt" + ^nu --testbin cococo "*.txt" "# )); - assert!(actual.err.contains("No such file or directory")); + assert_eq!(actual.out, "*.txt"); }) } -#[cfg(not(windows))] #[test] fn failed_command_with_semicolon_will_not_execute_following_cmds() { Playground::setup("external failed command with semicolon", |dirs, _| { let actual = nu!( cwd: dirs.test(), pipeline( " - ^ls *.abc; echo done + nu --testbin fail; echo done " )); @@ -155,16 +149,51 @@ fn external_args_with_quoted() { #[cfg(not(windows))] #[test] -fn external_arg_with_long_flag_value_quoted() { - Playground::setup("external failed command with semicolon", |dirs, _| { +fn external_arg_with_option_like_embedded_quotes() { + // TODO: would be nice to make this work with cococo, but arg parsing interferes + Playground::setup( + "external arg with option like embedded quotes", + |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^echo --foo='bar' -foo='bar' + "# + )); + + assert_eq!(actual.out, "--foo=bar -foo=bar"); + }, + ) +} + +#[test] +fn external_arg_with_non_option_like_embedded_quotes() { + Playground::setup( + "external arg with non option like embedded quotes", + |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^nu --testbin cococo foo='bar' 'foo'=bar + "# + )); + + assert_eq!(actual.out, "foo=bar foo=bar"); + }, + ) +} + +#[test] +fn external_arg_with_string_interpolation() { + Playground::setup("external arg with string interpolation", |dirs, _| { let actual = nu!( cwd: dirs.test(), pipeline( r#" - ^echo --foo='bar' + ^nu --testbin cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" "# )); - assert_eq!(actual.out, "--foo=bar"); + assert_eq!(actual.out, "foo=4 foo=4 foo=4"); }) } @@ -200,6 +229,67 @@ fn external_command_escape_args() { }) } +#[test] +#[cfg_attr( + not(target_os = "linux"), + ignore = "only runs on Linux, where controlling the HOME var is reliable" +)] +fn external_command_expand_tilde() { + Playground::setup("external command expand tilde", |dirs, _| { + // Make a copy of the nu executable that we can use + let mut src = std::fs::File::open(nu_test_support::fs::binaries().join("nu")) + .expect("failed to open nu"); + let mut dst = std::fs::File::create_new(dirs.test().join("test_nu")) + .expect("failed to create test_nu file"); + std::io::copy(&mut src, &mut dst).expect("failed to copy data for nu binary"); + + // Make test_nu have the same permissions so that it's executable + dst.set_permissions( + src.metadata() + .expect("failed to get nu metadata") + .permissions(), + ) + .expect("failed to set permissions on test_nu"); + + // Close the files + drop(dst); + drop(src); + + let actual = nu!( + envs: vec![ + ("HOME".to_string(), dirs.test().to_string_lossy().into_owned()), + ], + r#" + ^~/test_nu --testbin cococo hello + "# + ); + assert_eq!(actual.out, "hello"); + }) +} + +#[test] +fn external_arg_expand_tilde() { + Playground::setup("external arg expand tilde", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^nu --testbin cococo ~/foo ~/(2 + 2) + "# + )); + + let home = dirs_next::home_dir().expect("failed to find home dir"); + + assert_eq!( + actual.out, + format!( + "{} {}", + home.join("foo").display(), + home.join("4").display() + ) + ); + }) +} + #[test] fn external_command_not_expand_tilde_with_quotes() { Playground::setup( @@ -231,21 +321,6 @@ fn external_command_receives_raw_binary_data() { }) } -#[cfg(windows)] -#[test] -fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() { - Playground::setup("external failed command with semicolon", |dirs, _| { - let actual = nu!( - cwd: dirs.test(), pipeline( - " - ^cargo asdf; echo done - " - )); - - assert!(!actual.out.contains("done")); - }) -} - #[cfg(windows)] #[test] fn can_run_batch_files() { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 92b424783a..88638b2475 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -26,6 +26,7 @@ pub enum FlatShape { Flag, Float, Garbage, + GlobInterpolation, GlobPattern, Int, InternalCall(DeclId), @@ -67,6 +68,7 @@ impl FlatShape { FlatShape::Flag => "shape_flag", FlatShape::Float => "shape_float", FlatShape::Garbage => "shape_garbage", + FlatShape::GlobInterpolation => "shape_glob_interpolation", FlatShape::GlobPattern => "shape_globpattern", FlatShape::Int => "shape_int", FlatShape::InternalCall(_) => "shape_internalcall", @@ -277,7 +279,7 @@ fn flatten_expression_into( output[arg_start..].sort(); } Expr::ExternalCall(head, args) => { - if let Expr::String(..) = &head.expr { + if let Expr::String(..) | Expr::GlobPattern(..) = &head.expr { output.push((head.span, FlatShape::External)); } else { flatten_expression_into(working_set, head, output); @@ -286,7 +288,7 @@ fn flatten_expression_into( for arg in args.as_ref() { match arg { ExternalArgument::Regular(expr) => { - if let Expr::String(..) = &expr.expr { + if let Expr::String(..) | Expr::GlobPattern(..) = &expr.expr { output.push((expr.span, FlatShape::ExternalArg)); } else { flatten_expression_into(working_set, expr, output); @@ -431,6 +433,25 @@ fn flatten_expression_into( } output.extend(flattened); } + Expr::GlobInterpolation(exprs, quoted) => { + let mut flattened = vec![]; + for expr in exprs { + flatten_expression_into(working_set, expr, &mut flattened); + } + + if *quoted { + // If we aren't a bare word interpolation, also highlight the outer quotes + output.push(( + Span::new(expr.span.start, expr.span.start + 2), + FlatShape::GlobInterpolation, + )); + flattened.push(( + Span::new(expr.span.end - 1, expr.span.end), + FlatShape::GlobInterpolation, + )); + } + output.extend(flattened); + } Expr::Record(list) => { let outer_span = expr.span; let mut last_end = outer_span.start; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 149bce8958..ba718cc771 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -16,7 +16,6 @@ use nu_protocol::{ IN_VARIABLE_ID, }; use std::{ - borrow::Cow, collections::{HashMap, HashSet}, num::ParseIntError, str, @@ -222,6 +221,209 @@ pub(crate) fn check_call( } } +/// Parses a string in the arg or head position of an external call. +/// +/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes +/// or parentheses, it is parsed as a glob pattern so that tilde and glob expansion can be handled +/// by `run-external`. Otherwise, we use a custom state machine to put together an interpolated +/// string, where each balanced pair of quotes is parsed as a separate part of the string, and then +/// concatenated together. +/// +/// For example, `-foo="bar\nbaz"` becomes `$"-foo=bar\nbaz"` +fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expression { + let contents = &working_set.get_span_contents(span); + + if contents.starts_with(b"r#") { + parse_raw_string(working_set, span) + } else if contents + .iter() + .any(|b| matches!(b, b'"' | b'\'' | b'(' | b')')) + { + enum State { + Bare { + from: usize, + }, + Quote { + from: usize, + quote_char: u8, + escaped: bool, + depth: i32, + }, + } + // Find the spans of parts of the string that can be parsed as their own strings for + // concatenation. + // + // By passing each of these parts to `parse_string()`, we can eliminate the quotes and also + // handle string interpolation. + let make_span = |from: usize, index: usize| Span { + start: span.start + from, + end: span.start + index, + }; + let mut spans = vec![]; + let mut state = State::Bare { from: 0 }; + let mut index = 0; + while index < contents.len() { + let ch = contents[index]; + match &mut state { + State::Bare { from } => match ch { + b'"' | b'\'' => { + // Push bare string + if index != *from { + spans.push(make_span(*from, index)); + } + // then transition to other state + state = State::Quote { + from: index, + quote_char: ch, + escaped: false, + depth: 1, + }; + } + b'$' => { + if let Some("e_char @ (b'"' | b'\'')) = contents.get(index + 1) { + // Start a dollar quote (interpolated string) + if index != *from { + spans.push(make_span(*from, index)); + } + state = State::Quote { + from: index, + quote_char, + escaped: false, + depth: 1, + }; + // Skip over two chars (the dollar sign and the quote) + index += 2; + continue; + } + } + // Continue to consume + _ => (), + }, + State::Quote { + from, + quote_char, + escaped, + depth, + } => match ch { + ch if ch == *quote_char && !*escaped => { + // Count if there are more than `depth` quotes remaining + if contents[index..] + .iter() + .filter(|b| *b == quote_char) + .count() as i32 + > *depth + { + // Increment depth to be greedy + *depth += 1; + } else { + // Decrement depth + *depth -= 1; + } + if *depth == 0 { + // End of string + spans.push(make_span(*from, index + 1)); + // go back to Bare state + state = State::Bare { from: index + 1 }; + } + } + b'\\' if !*escaped && *quote_char == b'"' => { + // The next token is escaped so it doesn't count (only for double quote) + *escaped = true; + } + _ => { + *escaped = false; + } + }, + } + index += 1; + } + + // Add the final span + match state { + State::Bare { from } | State::Quote { from, .. } => { + if from < contents.len() { + spans.push(make_span(from, contents.len())); + } + } + } + + // Log the spans that will be parsed + if log::log_enabled!(log::Level::Trace) { + let contents = spans + .iter() + .map(|span| String::from_utf8_lossy(working_set.get_span_contents(*span))) + .collect::>(); + + trace!("parsing: external string, parts: {contents:?}") + } + + // Check if the whole thing is quoted. If not, it should be a glob + let quoted = + (contents.len() >= 3 && contents.starts_with(b"$\"") && contents.ends_with(b"\"")) + || is_quoted(contents); + + // Parse each as its own string + let exprs: Vec = spans + .into_iter() + .map(|span| parse_string(working_set, span)) + .collect(); + + if exprs + .iter() + .all(|expr| matches!(expr.expr, Expr::String(..))) + { + // If the exprs are all strings anyway, just collapse into a single string. + let string = exprs + .into_iter() + .map(|expr| { + let Expr::String(contents) = expr.expr else { + unreachable!("already checked that this was a String") + }; + contents + }) + .collect::(); + if quoted { + Expression::new(working_set, Expr::String(string), span, Type::String) + } else { + Expression::new( + working_set, + Expr::GlobPattern(string, false), + span, + Type::Glob, + ) + } + } else { + // Flatten any string interpolations contained with the exprs. + let exprs = exprs + .into_iter() + .flat_map(|expr| match expr.expr { + Expr::StringInterpolation(subexprs) => subexprs, + _ => vec![expr], + }) + .collect(); + // Make an interpolation out of the expressions. Use `GlobInterpolation` if it's a bare + // word, so that the unquoted state can get passed through to `run-external`. + if quoted { + Expression::new( + working_set, + Expr::StringInterpolation(exprs), + span, + Type::String, + ) + } else { + Expression::new( + working_set, + Expr::GlobInterpolation(exprs, false), + span, + Type::Glob, + ) + } + } + } else { + parse_glob_pattern(working_set, span) + } +} + fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument { let contents = working_set.get_span_contents(span); @@ -229,8 +431,6 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External ExternalArgument::Regular(parse_dollar_expr(working_set, span)) } else if contents.starts_with(b"[") { ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any)) - } else if contents.starts_with(b"r#") { - ExternalArgument::Regular(parse_raw_string(working_set, span)) } else if contents.len() > 3 && contents.starts_with(b"...") && (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(') @@ -241,18 +441,7 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External &SyntaxShape::List(Box::new(SyntaxShape::Any)), )) } else { - // Eval stage trims the quotes, so we don't have to do the same thing when parsing. - let (contents, err) = unescape_string_preserving_quotes(contents, span); - if let Some(err) = err { - working_set.error(err); - } - - ExternalArgument::Regular(Expression::new( - working_set, - Expr::String(contents), - span, - Type::String, - )) + ExternalArgument::Regular(parse_external_string(working_set, span)) } } @@ -274,18 +463,7 @@ pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) -> let arg = parse_expression(working_set, &[head_span]); Box::new(arg) } else { - // Eval stage will unquote the string, so we don't bother with that here - let (contents, err) = unescape_string_preserving_quotes(&head_contents, head_span); - if let Some(err) = err { - working_set.error(err) - } - - Box::new(Expression::new( - working_set, - Expr::String(contents), - head_span, - Type::String, - )) + Box::new(parse_external_string(working_set, head_span)) }; let args = spans[1..] @@ -2639,23 +2817,6 @@ pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option (String, Option) { - let (bytes, err) = if bytes.starts_with(b"\"") { - let (bytes, err) = unescape_string(bytes, span); - (Cow::Owned(bytes), err) - } else { - (Cow::Borrowed(bytes), None) - }; - - // The original code for args used lossy conversion here, even though that's not what we - // typically use for strings. Revisit whether that's actually desirable later, but don't - // want to introduce a breaking change for this patch. - let token = String::from_utf8_lossy(&bytes).into_owned(); - (token, err) -} - pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression { trace!("parsing: string"); @@ -6012,7 +6173,7 @@ pub fn discover_captures_in_expr( } Expr::String(_) => {} Expr::RawString(_) => {} - Expr::StringInterpolation(exprs) => { + Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => { for expr in exprs { discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; } diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 2646b3cc90..0784fe69d4 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,8 +1,8 @@ use nu_parser::*; use nu_protocol::{ - ast::{Argument, Call, Expr, ExternalArgument, PathMember, Range}, + ast::{Argument, Call, Expr, Expression, ExternalArgument, PathMember, Range}, engine::{Command, EngineState, Stack, StateWorkingSet}, - ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, + ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, }; use rstest::rstest; @@ -182,7 +182,7 @@ pub fn multi_test_parse_int() { Test( "ranges or relative paths not confused for int", b"./a/b", - Expr::String("./a/b".into()), + Expr::GlobPattern("./a/b".into(), false), None, ), Test( @@ -694,6 +694,50 @@ pub fn parse_call_missing_req_flag() { )); } +fn test_external_call(input: &str, tag: &str, f: impl FnOnce(&Expression, &[ExternalArgument])) { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + let block = parse(&mut working_set, None, input.as_bytes(), true); + assert!( + working_set.parse_errors.is_empty(), + "{tag}: errors: {:?}", + working_set.parse_errors + ); + + let pipeline = &block.pipelines[0]; + assert_eq!(1, pipeline.len()); + let element = &pipeline.elements[0]; + match &element.expr.expr { + Expr::ExternalCall(name, args) => f(name, args), + other => { + panic!("{tag}: Unexpected expression in pipeline: {other:?}"); + } + } +} + +fn check_external_call_interpolation( + tag: &str, + subexpr_count: usize, + quoted: bool, + expr: &Expression, +) -> bool { + match &expr.expr { + Expr::StringInterpolation(exprs) => { + assert!(quoted, "{tag}: quoted"); + assert_eq!(expr.ty, Type::String, "{tag}: expr.ty"); + assert_eq!(subexpr_count, exprs.len(), "{tag}: subexpr_count"); + true + } + Expr::GlobInterpolation(exprs, is_quoted) => { + assert_eq!(quoted, *is_quoted, "{tag}: quoted"); + assert_eq!(expr.ty, Type::Glob, "{tag}: expr.ty"); + assert_eq!(subexpr_count, exprs.len(), "{tag}: subexpr_count"); + true + } + _ => false, + } +} + #[rstest] #[case("foo-external-call", "foo-external-call", "bare word")] #[case("^foo-external-call", "foo-external-call", "bare word with caret")] @@ -713,200 +757,370 @@ pub fn parse_call_missing_req_flag() { r"foo\external-call", "bare word with backslash and caret" )] -#[case( - "^'foo external call'", - "'foo external call'", - "single quote with caret" -)] -#[case( - "^'foo/external call'", - "'foo/external call'", - "single quote with forward slash and caret" -)] -#[case( - r"^'foo\external call'", - r"'foo\external call'", - "single quote with backslash and caret" -)] -#[case( - r#"^"foo external call""#, - r#""foo external call""#, - "double quote with caret" -)] -#[case( - r#"^"foo/external call""#, - r#""foo/external call""#, - "double quote with forward slash and caret" -)] -#[case( - r#"^"foo\\external call""#, - r#""foo\external call""#, - "double quote with backslash and caret" -)] -#[case("`foo external call`", "`foo external call`", "backtick quote")] +#[case("`foo external call`", "foo external call", "backtick quote")] #[case( "^`foo external call`", - "`foo external call`", + "foo external call", "backtick quote with caret" )] #[case( "`foo/external call`", - "`foo/external call`", + "foo/external call", "backtick quote with forward slash" )] #[case( "^`foo/external call`", - "`foo/external call`", + "foo/external call", "backtick quote with forward slash and caret" )] #[case( - r"^`foo\external call`", r"`foo\external call`", + r"foo\external call", "backtick quote with backslash" )] #[case( r"^`foo\external call`", - r"`foo\external call`", + r"foo\external call", "backtick quote with backslash and caret" )] -fn test_external_call_name(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) { - let engine_state = EngineState::new(); - let mut working_set = StateWorkingSet::new(&engine_state); - let block = parse(&mut working_set, None, input.as_bytes(), true); - assert!( - working_set.parse_errors.is_empty(), - "{tag}: errors: {:?}", - working_set.parse_errors - ); - - let pipeline = &block.pipelines[0]; - assert_eq!(1, pipeline.len()); - let element = &pipeline.elements[0]; - match &element.expr.expr { - Expr::ExternalCall(name, args) => { - match &name.expr { - Expr::String(string) => { - assert_eq!(expected, string); - } - other => { - panic!("{tag}: Unexpected expression in command name position: {other:?}"); - } - } - assert_eq!(0, args.len()); - } - other => { - panic!("{tag}: Unexpected expression in pipeline: {other:?}"); - } - } -} - -#[rstest] -#[case("^foo bar-baz", "bar-baz", "bare word")] -#[case("^foo bar/baz", "bar/baz", "bare word with forward slash")] -#[case(r"^foo bar\baz", r"bar\baz", "bare word with backslash")] -#[case("^foo 'bar baz'", "'bar baz'", "single quote")] -#[case("foo 'bar/baz'", "'bar/baz'", "single quote with forward slash")] -#[case(r"foo 'bar\baz'", r"'bar\baz'", "single quote with backslash")] -#[case(r#"^foo "bar baz""#, r#""bar baz""#, "double quote")] -#[case(r#"^foo "bar/baz""#, r#""bar/baz""#, "double quote with forward slash")] -#[case(r#"^foo "bar\\baz""#, r#""bar\baz""#, "double quote with backslash")] -#[case("^foo `bar baz`", "`bar baz`", "backtick quote")] -#[case("^foo `bar/baz`", "`bar/baz`", "backtick quote with forward slash")] -#[case(r"^foo `bar\baz`", r"`bar\baz`", "backtick quote with backslash")] -fn test_external_call_argument_regular( +pub fn test_external_call_head_glob( #[case] input: &str, #[case] expected: &str, #[case] tag: &str, ) { - let engine_state = EngineState::new(); - let mut working_set = StateWorkingSet::new(&engine_state); - let block = parse(&mut working_set, None, input.as_bytes(), true); - assert!( - working_set.parse_errors.is_empty(), - "{tag}: errors: {:?}", - working_set.parse_errors - ); + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, is_quoted) => { + assert_eq!(expected, string, "{tag}: incorrect name"); + assert!(!*is_quoted); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(0, args.len()); + }) +} - let pipeline = &block.pipelines[0]; - assert_eq!(1, pipeline.len()); - let element = &pipeline.elements[0]; - match &element.expr.expr { - Expr::ExternalCall(name, args) => { - match &name.expr { - Expr::String(string) => { - assert_eq!("foo", string, "{tag}: incorrect name"); +#[rstest] +#[case( + r##"^r#'foo-external-call'#"##, + "foo-external-call", + "raw string with caret" +)] +#[case( + r##"^r#'foo/external-call'#"##, + "foo/external-call", + "raw string with forward slash and caret" +)] +#[case( + r##"^r#'foo\external-call'#"##, + r"foo\external-call", + "raw string with backslash and caret" +)] +pub fn test_external_call_head_raw_string( + #[case] input: &str, + #[case] expected: &str, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::RawString(string) => { + assert_eq!(expected, string, "{tag}: incorrect name"); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(0, args.len()); + }) +} + +#[rstest] +#[case("^'foo external call'", "foo external call", "single quote with caret")] +#[case( + "^'foo/external call'", + "foo/external call", + "single quote with forward slash and caret" +)] +#[case( + r"^'foo\external call'", + r"foo\external call", + "single quote with backslash and caret" +)] +#[case( + r#"^"foo external call""#, + r#"foo external call"#, + "double quote with caret" +)] +#[case( + r#"^"foo/external call""#, + r#"foo/external call"#, + "double quote with forward slash and caret" +)] +#[case( + r#"^"foo\\external call""#, + r#"foo\external call"#, + "double quote with backslash and caret" +)] +pub fn test_external_call_head_string( + #[case] input: &str, + #[case] expected: &str, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::String(string) => { + assert_eq!(expected, string); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(0, args.len()); + }) +} + +#[rstest] +#[case(r"~/.foo/(1)", 2, false, "unquoted interpolated string")] +#[case( + r"~\.foo(2)\(1)", + 4, + false, + "unquoted interpolated string with backslash" +)] +#[case(r"^~/.foo/(1)", 2, false, "unquoted interpolated string with caret")] +#[case(r#"^$"~/.foo/(1)""#, 2, true, "quoted interpolated string with caret")] +pub fn test_external_call_head_interpolated_string( + #[case] input: &str, + #[case] subexpr_count: usize, + #[case] quoted: bool, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + if !check_external_call_interpolation(tag, subexpr_count, quoted, name) { + panic!("{tag}: Unexpected expression in command name position: {name:?}"); + } + assert_eq!(0, args.len()); + }) +} + +#[rstest] +#[case("^foo foo-external-call", "foo-external-call", "bare word")] +#[case( + "^foo foo/external-call", + "foo/external-call", + "bare word with forward slash" +)] +#[case( + r"^foo foo\external-call", + r"foo\external-call", + "bare word with backslash" +)] +#[case( + "^foo `foo external call`", + "foo external call", + "backtick quote with caret" +)] +#[case( + "^foo `foo/external call`", + "foo/external call", + "backtick quote with forward slash" +)] +#[case( + r"^foo `foo\external call`", + r"foo\external call", + "backtick quote with backslash" +)] +pub fn test_external_call_arg_glob(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, _) => { + assert_eq!("foo", string, "{tag}: incorrect name"); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(1, args.len()); + match &args[0] { + ExternalArgument::Regular(expr) => match &expr.expr { + Expr::GlobPattern(string, is_quoted) => { + assert_eq!(expected, string, "{tag}: incorrect arg"); + assert!(!*is_quoted); } other => { - panic!("{tag}: Unexpected expression in command name position: {other:?}"); - } - } - assert_eq!(1, args.len()); - match &args[0] { - ExternalArgument::Regular(expr) => match &expr.expr { - Expr::String(string) => { - assert_eq!(expected, string, "{tag}: incorrect arg"); - } - other => { - panic!("Unexpected expression in command arg position: {other:?}") - } - }, - other @ ExternalArgument::Spread(..) => { - panic!("Unexpected external spread argument in command arg position: {other:?}") + panic!("Unexpected expression in command arg position: {other:?}") } + }, + other @ ExternalArgument::Spread(..) => { + panic!("Unexpected external spread argument in command arg position: {other:?}") } } - other => { - panic!("{tag}: Unexpected expression in pipeline: {other:?}"); + }) +} + +#[rstest] +#[case(r##"^foo r#'foo-external-call'#"##, "foo-external-call", "raw string")] +#[case( + r##"^foo r#'foo/external-call'#"##, + "foo/external-call", + "raw string with forward slash" +)] +#[case( + r##"^foo r#'foo\external-call'#"##, + r"foo\external-call", + "raw string with backslash" +)] +pub fn test_external_call_arg_raw_string( + #[case] input: &str, + #[case] expected: &str, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, _) => { + assert_eq!("foo", string, "{tag}: incorrect name"); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } } - } + assert_eq!(1, args.len()); + match &args[0] { + ExternalArgument::Regular(expr) => match &expr.expr { + Expr::RawString(string) => { + assert_eq!(expected, string, "{tag}: incorrect arg"); + } + other => { + panic!("Unexpected expression in command arg position: {other:?}") + } + }, + other @ ExternalArgument::Spread(..) => { + panic!("Unexpected external spread argument in command arg position: {other:?}") + } + } + }) +} + +#[rstest] +#[case("^foo 'foo external call'", "foo external call", "single quote")] +#[case( + "^foo 'foo/external call'", + "foo/external call", + "single quote with forward slash" +)] +#[case( + r"^foo 'foo\external call'", + r"foo\external call", + "single quote with backslash" +)] +#[case(r#"^foo "foo external call""#, r#"foo external call"#, "double quote")] +#[case( + r#"^foo "foo/external call""#, + r#"foo/external call"#, + "double quote with forward slash" +)] +#[case( + r#"^foo "foo\\external call""#, + r#"foo\external call"#, + "double quote with backslash" +)] +pub fn test_external_call_arg_string( + #[case] input: &str, + #[case] expected: &str, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, _) => { + assert_eq!("foo", string, "{tag}: incorrect name"); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(1, args.len()); + match &args[0] { + ExternalArgument::Regular(expr) => match &expr.expr { + Expr::String(string) => { + assert_eq!(expected, string, "{tag}: incorrect arg"); + } + other => { + panic!("{tag}: Unexpected expression in command arg position: {other:?}") + } + }, + other @ ExternalArgument::Spread(..) => { + panic!( + "{tag}: Unexpected external spread argument in command arg position: {other:?}" + ) + } + } + }) +} + +#[rstest] +#[case(r"^foo ~/.foo/(1)", 2, false, "unquoted interpolated string")] +#[case(r#"^foo $"~/.foo/(1)""#, 2, true, "quoted interpolated string")] +pub fn test_external_call_arg_interpolated_string( + #[case] input: &str, + #[case] subexpr_count: usize, + #[case] quoted: bool, + #[case] tag: &str, +) { + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, _) => { + assert_eq!("foo", string, "{tag}: incorrect name"); + } + other => { + panic!("{tag}: Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(1, args.len()); + match &args[0] { + ExternalArgument::Regular(expr) => { + if !check_external_call_interpolation(tag, subexpr_count, quoted, expr) { + panic!("Unexpected expression in command arg position: {expr:?}") + } + } + other @ ExternalArgument::Spread(..) => { + panic!("Unexpected external spread argument in command arg position: {other:?}") + } + } + }) } #[test] fn test_external_call_argument_spread() { - let engine_state = EngineState::new(); - let mut working_set = StateWorkingSet::new(&engine_state); - let block = parse(&mut working_set, None, b"^foo ...[a b c]", true); - assert!( - working_set.parse_errors.is_empty(), - "errors: {:?}", - working_set.parse_errors - ); + let input = r"^foo ...[a b c]"; + let tag = "spread"; - let pipeline = &block.pipelines[0]; - assert_eq!(1, pipeline.len()); - let element = &pipeline.elements[0]; - match &element.expr.expr { - Expr::ExternalCall(name, args) => { - match &name.expr { - Expr::String(string) => { - assert_eq!("foo", string, "incorrect name"); + test_external_call(input, tag, |name, args| { + match &name.expr { + Expr::GlobPattern(string, _) => { + assert_eq!("foo", string, "incorrect name"); + } + other => { + panic!("Unexpected expression in command name position: {other:?}"); + } + } + assert_eq!(1, args.len()); + match &args[0] { + ExternalArgument::Spread(expr) => match &expr.expr { + Expr::List(items) => { + assert_eq!(3, items.len()); + // that's good enough, don't really need to go so deep into it... } other => { - panic!("Unexpected expression in command name position: {other:?}"); - } - } - assert_eq!(1, args.len()); - match &args[0] { - ExternalArgument::Spread(expr) => match &expr.expr { - Expr::List(items) => { - assert_eq!(3, items.len()); - // that's good enough, don't really need to go so deep into it... - } - other => { - panic!("Unexpected expression in command arg position: {other:?}") - } - }, - other @ ExternalArgument::Regular(..) => { - panic!( - "Unexpected external regular argument in command arg position: {other:?}" - ) + panic!("Unexpected expression in command arg position: {other:?}") } + }, + other @ ExternalArgument::Regular(..) => { + panic!("Unexpected external regular argument in command arg position: {other:?}") } } - other => { - panic!("Unexpected expression in pipeline: {other:?}"); - } - } + }) } #[test] @@ -1132,6 +1346,44 @@ mod string { assert_eq!(subexprs[0], &Expr::String("(1 + 3)(7 - 5)".to_string())); } + #[test] + pub fn parse_string_interpolation_bare() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let block = parse( + &mut working_set, + None, + b"\"\" ++ foo(1 + 3)bar(7 - 5)", + true, + ); + + assert!(working_set.parse_errors.is_empty()); + + assert_eq!(block.len(), 1); + let pipeline = &block.pipelines[0]; + assert_eq!(pipeline.len(), 1); + let element = &pipeline.elements[0]; + assert!(element.redirection.is_none()); + + let subexprs: Vec<&Expr> = match &element.expr.expr { + Expr::BinaryOp(_, _, rhs) => match &rhs.expr { + Expr::StringInterpolation(expressions) => { + expressions.iter().map(|e| &e.expr).collect() + } + _ => panic!("Expected an `Expr::StringInterpolation`"), + }, + _ => panic!("Expected an `Expr::BinaryOp`"), + }; + + assert_eq!(subexprs.len(), 4); + + assert_eq!(subexprs[0], &Expr::String("foo".to_string())); + assert!(matches!(subexprs[1], &Expr::FullCellPath(..))); + assert_eq!(subexprs[2], &Expr::String("bar".to_string())); + assert!(matches!(subexprs[3], &Expr::FullCellPath(..))); + } + #[test] pub fn parse_nested_expressions() { let engine_state = EngineState::new(); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 13d9e42985..0e561e5c8f 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -32,8 +32,11 @@ pub enum Expr { Keyword(Box), ValueWithUnit(Box), DateTime(chrono::DateTime), + /// The boolean is `true` if the string is quoted. Filepath(String, bool), + /// The boolean is `true` if the string is quoted. Directory(String, bool), + /// The boolean is `true` if the string is quoted. GlobPattern(String, bool), String(String), RawString(String), @@ -43,6 +46,8 @@ pub enum Expr { Overlay(Option), // block ID of the overlay's origin module Signature(Box), StringInterpolation(Vec), + /// The boolean is `true` if the string is quoted. + GlobInterpolation(Vec, bool), Nothing, Garbage, } @@ -84,6 +89,7 @@ impl Expr { | Expr::RawString(_) | Expr::CellPath(_) | Expr::StringInterpolation(_) + | Expr::GlobInterpolation(_, _) | Expr::Nothing => { // These expressions do not use the output of the pipeline in any meaningful way, // so we can discard the previous output by redirecting it to `Null`. diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 08ecc59aaf..040e140cab 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -232,7 +232,7 @@ impl Expression { } false } - Expr::StringInterpolation(items) => { + Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => { for i in items { if i.has_in_variable(working_set) { return true; @@ -441,7 +441,7 @@ impl Expression { Expr::Signature(_) => {} Expr::String(_) => {} Expr::RawString(_) => {} - Expr::StringInterpolation(items) => { + Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => { for i in items { i.replace_span(working_set, replaced, new_span) } diff --git a/crates/nu-protocol/src/debugger/profiler.rs b/crates/nu-protocol/src/debugger/profiler.rs index 0fa2d4f3aa..c3066c0a14 100644 --- a/crates/nu-protocol/src/debugger/profiler.rs +++ b/crates/nu-protocol/src/debugger/profiler.rs @@ -258,6 +258,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String { Expr::Signature(_) => "signature".to_string(), Expr::String(_) | Expr::RawString(_) => "string".to_string(), Expr::StringInterpolation(_) => "string interpolation".to_string(), + Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(), Expr::Subexpression(_) => "subexpression".to_string(), Expr::Table(_) => "table".to_string(), Expr::UnaryNot(_) => "unary not".to_string(), diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 2317149460..7e1e2b0a7c 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -290,6 +290,15 @@ pub trait Eval { Ok(Value::string(str, expr_span)) } + Expr::GlobInterpolation(exprs, quoted) => { + let config = Self::get_config(state, mut_state); + let str = exprs + .iter() + .map(|expr| Self::eval::(state, mut_state, expr).map(|v| v.to_expanded_string(", ", &config))) + .collect::>()?; + + Ok(Value::glob(str, *quoted, expr_span)) + } Expr::Overlay(_) => Self::eval_overlay(state, expr_span), Expr::GlobPattern(pattern, quoted) => { // GlobPattern is similar to Filepath diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index 51d743e1a4..e83b4354da 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -245,6 +245,7 @@ use tempfile::tempdir; pub struct NuOpts { pub cwd: Option, pub locale: Option, + pub envs: Option>, pub collapse_output: Option, } @@ -278,6 +279,11 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O command .env(nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR, locale) .env(NATIVE_PATH_ENV_VAR, paths_joined); + + if let Some(envs) = opts.envs { + command.envs(envs); + } + // Ensure that the user's config doesn't interfere with the tests command.arg("--no-config-file"); if !with_std { diff --git a/crates/nuon/src/from.rs b/crates/nuon/src/from.rs index f9230d6140..2cfeaebc61 100644 --- a/crates/nuon/src/from.rs +++ b/crates/nuon/src/from.rs @@ -323,6 +323,12 @@ fn convert_to_value( msg: "string interpolation not supported in nuon".into(), span: expr.span, }), + Expr::GlobInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError { + src: original_text.to_string(), + error: "Error when loading".into(), + msg: "glob interpolation not supported in nuon".into(), + span: expr.span, + }), Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError { src: original_text.to_string(), error: "Error when loading".into(), From 26bdba206890acb1de87cf0636bbccc9520e9bc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:10:27 +0800 Subject: [PATCH 06/38] Bump interprocess from 2.1.0 to 2.2.0 (#13178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [interprocess](https://github.com/kotauskas/interprocess) from 2.1.0 to 2.2.0.
Release notes

Sourced from interprocess's releases.

2.2.0 – Tokio unnamed pipes

  • Tokio-based unnamed pipes, with subpar performance on Windows due to OS API limitations
  • Examples for unnamed pipes, both non-async and Tokio
  • Impersonation for Windows named pipes
  • Improvements to the implementation of Windows pipe flushing on Tokio

2.1.1

  • Removed async Incoming and futures::Stream ("AsyncIterator") implementations on local_socket::traits::Listener implementors – those were actually completely broken, so this change is not breaking in practice and thus does not warrant a bump to 3.0.0
  • Fixed ListenerOptionsExt::mode() behavior in umask fallback mode and improved its documentation
  • Moved examples to their own dedicated files with the help of the doctest-file crate
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=interprocess&package-manager=cargo&previous-version=2.1.0&new-version=2.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 11 +++++++++-- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0db346b785..ba1880d9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1227,6 +1227,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -2026,10 +2032,11 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4d0250d41da118226e55b3d50ca3f0d9e0a0f6829b92f543ac0054aeea1572" +checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3" dependencies = [ + "doctest-file", "libc", "recvmsg", "widestring", diff --git a/Cargo.toml b/Cargo.toml index 18f45987e7..6fce7d3c01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ heck = "0.5.0" human-date-parser = "0.1.1" indexmap = "2.2" indicatif = "0.17" -interprocess = "2.1.0" +interprocess = "2.2.0" is_executable = "1.0" itertools = "0.12" libc = "0.2" From bb6cb94e559c2f0a690b468949e5a40ba52b082f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:10:39 +0800 Subject: [PATCH 07/38] Bump actions/checkout from 4.1.6 to 4.1.7 (#13177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
Release notes

Sourced from actions/checkout's releases.

v4.1.7

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.6...v4.1.7

Changelog

Sourced from actions/checkout's changelog.

v4.1.7

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.6&new-version=4.1.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/audit.yml | 2 +- .github/workflows/ci.yml | 8 ++++---- .github/workflows/nightly-build.yml | 6 +++--- .github/workflows/release.yml | 2 +- .github/workflows/typos.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index a0295b3b4d..694ff6e1bb 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -19,7 +19,7 @@ jobs: # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: true steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - uses: rustsec/audit-check@v1.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb94e5fe8b..25fcfde55e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Setup Rust toolchain and cache uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 @@ -66,7 +66,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Setup Rust toolchain and cache uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 @@ -95,7 +95,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Setup Rust toolchain and cache uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 @@ -146,7 +146,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Setup Rust toolchain and cache uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 4f6dccbcf9..9a30cae614 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -27,7 +27,7 @@ jobs: # if: github.repository == 'nushell/nightly' steps: - name: Checkout - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 if: github.repository == 'nushell/nightly' with: ref: main @@ -112,7 +112,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: ref: main fetch-depth: 0 @@ -181,7 +181,7 @@ jobs: - name: Waiting for Release run: sleep 1800 - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: ref: main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a71792e8c8..31fb1197a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Update Rust Toolchain Target run: | diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index d33750e337..33e7ac0945 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Check spelling uses: crate-ci/typos@v1.22.7 From c09a8a5ec954974cf22ae2e4d7f2c99628272116 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 20 Jun 2024 06:30:43 -0500 Subject: [PATCH 08/38] add a system level folder for future autoloading (#13180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds a directory to the `$nu` constant that shows where the system level autoload directory is located at. This folder is modifiable at compile time with environment variables. ```rust // Create a system level directory for nushell scripts, modules, completions, etc // that can be changed by setting the NU_VENDOR_AUTOLOAD_DIR env var on any platform // before nushell is compiled OR if NU_VENDOR_AUTOLOAD_DIR is not set for non-windows // systems, the PREFIX env var can be set before compile and used as PREFIX/nushell/vendor/autoload // pseudo code // if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it // if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload // if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload // if not, use the default /usr/share/nushell/vendor/autoload ``` ### Windows default ```nushell ❯ $nu.vendor-autoload-dir C:\ProgramData\nushell\vendor\autoload ``` ### Non-Windows default ```nushell ❯ $nu.vendor-autoload-dir /usr/local/share/nushell/vendor/autoload ``` ### Non-Windows with PREFIX set ```nushell ❯ PREFIX=/usr/bob cargo r ❯ $nu.vendor-autoload-dir /usr/bob/share/nushell/vendor/autoload ``` ### Non-Windows with NU_VENDOR_AUTOLOAD_DIR set ```nushell ❯ NU_VENDOR_AUTOLOAD_DIR=/some/other/path/nushell/stuff cargo r ❯ $nu.vendor-autoload-dir /some/other/path/nushell/stuff ``` > [!IMPORTANT] To be clear, this PR does not do the auto-loading, it just sets up the folder to support that functionality that can be added in a later PR. The PR also does not create the folder defined. It's just setting the $nu constant.   # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-cli/tests/completions/mod.rs | 3 +- crates/nu-protocol/src/eval_const.rs | 40 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 107de98c80..35e9435b41 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -763,7 +763,7 @@ fn variables_completions() { // Test completions for $nu let suggestions = completer.complete("$nu.", 4); - assert_eq!(17, suggestions.len()); + assert_eq!(18, suggestions.len()); let expected: Vec = vec![ "cache-dir".into(), @@ -783,6 +783,7 @@ fn variables_completions() { "plugin-path".into(), "startup-time".into(), "temp-path".into(), + "vendor-autoload-dir".into(), ]; // Match results diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 9f81a6f38b..8f6382ae44 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -181,6 +181,46 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu }, ); + // Create a system level directory for nushell scripts, modules, completions, etc + // that can be changed by setting the NU_VENDOR_AUTOLOAD_DIR env var on any platform + // before nushell is compiled OR if NU_VENDOR_AUTOLOAD_DIR is not set for non-windows + // systems, the PREFIX env var can be set before compile and used as PREFIX/nushell/vendor/autoload + record.push( + "vendor-autoload-dir", + // pseudo code + // if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it + // if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload + // if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload + // if not, use the default /usr/share/nushell/vendor/autoload + + // check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default + Value::string( + option_env!("NU_VENDOR_AUTOLOAD_DIR") + .map(String::from) + .unwrap_or_else(|| { + if cfg!(windows) { + let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") { + Some(v) => format!( + "{}\\nushell\\vendor\\autoload", + v.coerce_string().unwrap_or("C:\\ProgramData".into()) + ), + None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(), + }; + all_user_profile + } else { + // In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set + // check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload + // otherwise default to /usr/share/nushell/vendor/autoload + option_env!("PREFIX").map(String::from).map_or_else( + || "/usr/local/share/nushell/vendor/autoload".into(), + |prefix| format!("{}/share/nushell/vendor/autoload", prefix), + ) + } + }), + span, + ), + ); + record.push("temp-path", { let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); Value::string(canon_temp_path.to_string_lossy(), span) From 7d2d573eb8bbc14faf37dc3f3bbba658e18e475f Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:55:49 -0700 Subject: [PATCH 09/38] Added the ability to open json lines dataframes with polars lazy json lines reader. (#13167) The `--lazy` flag will now use the polars' LazyJsonLinesReader when opening a json lines file with `polars open` --- .../src/dataframe/eager/open.rs | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/crates/nu_plugin_polars/src/dataframe/eager/open.rs b/crates/nu_plugin_polars/src/dataframe/eager/open.rs index 13a65074f0..902a4df5a9 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/open.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/open.rs @@ -19,9 +19,12 @@ use std::{ sync::Arc, }; -use polars::prelude::{ - CsvEncoding, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader, LazyFrame, - ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader, +use polars::{ + lazy::frame::LazyJsonLineReader, + prelude::{ + CsvEncoding, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader, + LazyFrame, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader, + }, }; use polars_io::{ @@ -375,36 +378,51 @@ fn from_jsonl( .get_flag("schema")? .map(|schema| NuSchema::try_from(&schema)) .transpose()?; - let file = File::open(file_path).map_err(|e| ShellError::GenericError { - error: "Error opening file".into(), - msg: e.to_string(), - span: Some(file_span), - help: None, - inner: vec![], - })?; - - let buf_reader = BufReader::new(file); - let reader = JsonReader::new(buf_reader) - .with_json_format(JsonFormat::JsonLines) - .infer_schema_len(infer_schema); - - let reader = match maybe_schema { - Some(schema) => reader.with_schema(schema.into()), - None => reader, - }; - - let df: NuDataFrame = reader - .finish() - .map_err(|e| ShellError::GenericError { - error: "Json lines reader error".into(), - msg: format!("{e:?}"), - span: Some(call.head), + if call.has_flag("lazy")? { + let df = LazyJsonLineReader::new(file_path) + .with_infer_schema_length(infer_schema) + .with_schema(maybe_schema.map(|s| s.into())) + .finish() + .map_err(|e| ShellError::GenericError { + error: format!("Json lines reader error: {e}"), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + })?; + let df = NuLazyFrame::new(false, df); + df.cache_and_to_value(plugin, engine, call.head) + } else { + let file = File::open(file_path).map_err(|e| ShellError::GenericError { + error: "Error opening file".into(), + msg: e.to_string(), + span: Some(file_span), help: None, inner: vec![], - })? - .into(); + })?; + let buf_reader = BufReader::new(file); + let reader = JsonReader::new(buf_reader) + .with_json_format(JsonFormat::JsonLines) + .infer_schema_len(infer_schema); - df.cache_and_to_value(plugin, engine, call.head) + let reader = match maybe_schema { + Some(schema) => reader.with_schema(schema.into()), + None => reader, + }; + + let df: NuDataFrame = reader + .finish() + .map_err(|e| ShellError::GenericError { + error: "Json lines reader error".into(), + msg: format!("{e:?}"), + span: Some(call.head), + help: None, + inner: vec![], + })? + .into(); + + df.cache_and_to_value(plugin, engine, call.head) + } } fn from_csv( From 20834c9d47b25182d944e7db61fc3c944e11b2cc Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:37:38 -0700 Subject: [PATCH 10/38] Added the ability to turn on performance debugging through and env var for the polars plugin (#13191) This allows performance debugging to be turned on by setting: ```nushell $env.POLARS_PLUGIN_PERF = "true" ``` Furthermore, this improves the other plugin debugging by allowing the env variable for debugging to be set at any time versus having to be available when nushell is launched: ```nushell $env.POLARS_PLUGIN_DEBUG = "true" ``` This plugin introduces a `perf` function that will output timing results. This works very similar to the perf function available in nu_utils::utils::perf. This version prints everything to std error to not break the plugin stream and uses the engine interface to see if the env variable is configured. This pull requests uses this `perf` function when: * opening csv files as dataframes * opening json lines files as dataframes This will hopefully help provide some more fine grained information on how long it takes polars to open different dataframes. The `perf` can also be utilized later for other dataframes use cases. --- crates/nu_plugin_polars/src/cache/mod.rs | 41 ++++---- crates/nu_plugin_polars/src/cache/rm.rs | 2 +- .../src/dataframe/eager/open.rs | 46 +++++++++ crates/nu_plugin_polars/src/lib.rs | 94 +++++++++++++++++-- 4 files changed, 154 insertions(+), 29 deletions(-) diff --git a/crates/nu_plugin_polars/src/cache/mod.rs b/crates/nu_plugin_polars/src/cache/mod.rs index 8862f5bb51..08a7a9d7f7 100644 --- a/crates/nu_plugin_polars/src/cache/mod.rs +++ b/crates/nu_plugin_polars/src/cache/mod.rs @@ -13,7 +13,7 @@ use nu_plugin::{EngineInterface, PluginCommand}; use nu_protocol::{LabeledError, ShellError, Span}; use uuid::Uuid; -use crate::{plugin_debug, values::PolarsPluginObject, PolarsPlugin}; +use crate::{plugin_debug, values::PolarsPluginObject, EngineWrapper, PolarsPlugin}; #[derive(Debug, Clone)] pub struct CacheValue { @@ -47,7 +47,7 @@ impl Cache { /// * `force` - Delete even if there are multiple references pub fn remove( &self, - maybe_engine: Option<&EngineInterface>, + engine: impl EngineWrapper, key: &Uuid, force: bool, ) -> Result, ShellError> { @@ -60,22 +60,23 @@ impl Cache { let removed = if force || reference_count.unwrap_or_default() < 1 { let removed = lock.remove(key); - plugin_debug!("PolarsPlugin: removing {key} from cache: {removed:?}"); + plugin_debug!( + engine, + "PolarsPlugin: removing {key} from cache: {removed:?}" + ); removed } else { - plugin_debug!("PolarsPlugin: decrementing reference count for {key}"); + plugin_debug!( + engine, + "PolarsPlugin: decrementing reference count for {key}" + ); None }; // Once there are no more entries in the cache // we can turn plugin gc back on - match maybe_engine { - Some(engine) if lock.is_empty() => { - plugin_debug!("PolarsPlugin: Cache is empty enabling GC"); - engine.set_gc_disabled(false).map_err(LabeledError::from)?; - } - _ => (), - }; + plugin_debug!(engine, "PolarsPlugin: Cache is empty enabling GC"); + engine.set_gc_disabled(false).map_err(LabeledError::from)?; drop(lock); Ok(removed) } @@ -84,23 +85,21 @@ impl Cache { /// The maybe_engine parameter is required outside of testing pub fn insert( &self, - maybe_engine: Option<&EngineInterface>, + engine: impl EngineWrapper, uuid: Uuid, value: PolarsPluginObject, span: Span, ) -> Result, ShellError> { let mut lock = self.lock()?; - plugin_debug!("PolarsPlugin: Inserting {uuid} into cache: {value:?}"); + plugin_debug!( + engine, + "PolarsPlugin: Inserting {uuid} into cache: {value:?}" + ); // turn off plugin gc the first time an entry is added to the cache // as we don't want the plugin to be garbage collected if there // is any live data - match maybe_engine { - Some(engine) if lock.is_empty() => { - plugin_debug!("PolarsPlugin: Cache has values disabling GC"); - engine.set_gc_disabled(true).map_err(LabeledError::from)?; - } - _ => (), - }; + plugin_debug!(engine, "PolarsPlugin: Cache has values disabling GC"); + engine.set_gc_disabled(true).map_err(LabeledError::from)?; let cache_value = CacheValue { uuid, value, @@ -154,7 +153,7 @@ pub trait Cacheable: Sized + Clone { span: Span, ) -> Result { plugin.cache.insert( - Some(engine), + engine, self.cache_id().to_owned(), self.to_cache_value()?, span, diff --git a/crates/nu_plugin_polars/src/cache/rm.rs b/crates/nu_plugin_polars/src/cache/rm.rs index 5918209f32..9113f39cec 100644 --- a/crates/nu_plugin_polars/src/cache/rm.rs +++ b/crates/nu_plugin_polars/src/cache/rm.rs @@ -63,7 +63,7 @@ fn remove_cache_entry( let key = as_uuid(key, span)?; let msg = plugin .cache - .remove(Some(engine), &key, true)? + .remove(engine, &key, true)? .map(|_| format!("Removed: {key}")) .unwrap_or_else(|| format!("No value found for key: {key}")); Ok(Value::string(msg, span)) diff --git a/crates/nu_plugin_polars/src/dataframe/eager/open.rs b/crates/nu_plugin_polars/src/dataframe/eager/open.rs index 902a4df5a9..8842e3c7fb 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/open.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/open.rs @@ -1,5 +1,6 @@ use crate::{ dataframe::values::NuSchema, + perf, values::{CustomValueSupport, NuLazyFrame}, PolarsPlugin, }; @@ -378,7 +379,10 @@ fn from_jsonl( .get_flag("schema")? .map(|schema| NuSchema::try_from(&schema)) .transpose()?; + if call.has_flag("lazy")? { + let start_time = std::time::Instant::now(); + let df = LazyJsonLineReader::new(file_path) .with_infer_schema_length(infer_schema) .with_schema(maybe_schema.map(|s| s.into())) @@ -390,6 +394,16 @@ fn from_jsonl( help: None, inner: vec![], })?; + + perf( + engine, + "Lazy json lines dataframe open", + start_time, + file!(), + line!(), + column!(), + ); + let df = NuLazyFrame::new(false, df); df.cache_and_to_value(plugin, engine, call.head) } else { @@ -410,6 +424,8 @@ fn from_jsonl( None => reader, }; + let start_time = std::time::Instant::now(); + let df: NuDataFrame = reader .finish() .map_err(|e| ShellError::GenericError { @@ -421,6 +437,15 @@ fn from_jsonl( })? .into(); + perf( + engine, + "Eager json lines dataframe open", + start_time, + file!(), + line!(), + column!(), + ); + df.cache_and_to_value(plugin, engine, call.head) } } @@ -484,6 +509,7 @@ fn from_csv( Some(r) => csv_reader.with_skip_rows(r), }; + let start_time = std::time::Instant::now(); let df: NuLazyFrame = csv_reader .finish() .map_err(|e| ShellError::GenericError { @@ -495,8 +521,18 @@ fn from_csv( })? .into(); + perf( + engine, + "Lazy CSV dataframe open", + start_time, + file!(), + line!(), + column!(), + ); + df.cache_and_to_value(plugin, engine, call.head) } else { + let start_time = std::time::Instant::now(); let df = CsvReadOptions::default() .with_has_header(!no_header) .with_infer_schema_length(infer_schema) @@ -529,6 +565,16 @@ fn from_csv( help: None, inner: vec![], })?; + + perf( + engine, + "Eager CSV dataframe open", + start_time, + file!(), + line!(), + column!(), + ); + let df = NuDataFrame::new(false, df); df.cache_and_to_value(plugin, engine, call.head) } diff --git a/crates/nu_plugin_polars/src/lib.rs b/crates/nu_plugin_polars/src/lib.rs index 3baca54ad9..76da2eb039 100644 --- a/crates/nu_plugin_polars/src/lib.rs +++ b/crates/nu_plugin_polars/src/lib.rs @@ -8,25 +8,89 @@ use nu_plugin::{EngineInterface, Plugin, PluginCommand}; mod cache; pub mod dataframe; pub use dataframe::*; -use nu_protocol::{ast::Operator, CustomValue, LabeledError, Spanned, Value}; +use nu_protocol::{ast::Operator, CustomValue, LabeledError, ShellError, Span, Spanned, Value}; use crate::{ eager::eager_commands, expressions::expr_commands, lazy::lazy_commands, series::series_commands, values::PolarsPluginCustomValue, }; +pub trait EngineWrapper { + fn get_env_var(&self, key: &str) -> Option; + fn use_color(&self) -> bool; + fn set_gc_disabled(&self, disabled: bool) -> Result<(), ShellError>; +} + +impl EngineWrapper for &EngineInterface { + fn get_env_var(&self, key: &str) -> Option { + EngineInterface::get_env_var(self, key) + .ok() + .flatten() + .map(|x| match x { + Value::String { val, .. } => val, + _ => "".to_string(), + }) + } + + fn use_color(&self) -> bool { + self.get_config() + .ok() + .and_then(|config| config.color_config.get("use_color").cloned()) + .unwrap_or(Value::bool(false, Span::unknown())) + .is_true() + } + + fn set_gc_disabled(&self, disabled: bool) -> Result<(), ShellError> { + EngineInterface::set_gc_disabled(self, disabled) + } +} + #[macro_export] macro_rules! plugin_debug { - ($($arg:tt)*) => {{ - if std::env::var("POLARS_PLUGIN_DEBUG") - .ok() - .filter(|x| x == "1" || x == "true") + ($env_var_provider:tt, $($arg:tt)*) => {{ + if $env_var_provider.get_env_var("POLARS_PLUGIN_DEBUG") + .filter(|s| s == "1" || s == "true") .is_some() { eprintln!($($arg)*); } }}; } +pub fn perf( + env: impl EngineWrapper, + msg: &str, + dur: std::time::Instant, + file: &str, + line: u32, + column: u32, +) { + if env + .get_env_var("POLARS_PLUGIN_PERF") + .filter(|s| s == "1" || s == "true") + .is_some() + { + if env.use_color() { + eprintln!( + "perf: {}:{}:{} \x1b[32m{}\x1b[0m took \x1b[33m{:?}\x1b[0m", + file, + line, + column, + msg, + dur.elapsed(), + ); + } else { + eprintln!( + "perf: {}:{}:{} {} took {:?}", + file, + line, + column, + msg, + dur.elapsed(), + ); + } + } +} + #[derive(Default)] pub struct PolarsPlugin { pub(crate) cache: Cache, @@ -52,7 +116,7 @@ impl Plugin for PolarsPlugin { ) -> Result<(), LabeledError> { if !self.disable_cache_drop { let id = CustomValueType::try_from_custom_value(custom_value)?.id(); - let _ = self.cache.remove(Some(engine), &id, false); + let _ = self.cache.remove(engine, &id, false); } Ok(()) } @@ -193,6 +257,22 @@ pub mod test { } } + struct TestEngineWrapper; + + impl EngineWrapper for TestEngineWrapper { + fn get_env_var(&self, key: &str) -> Option { + std::env::var(key).ok() + } + + fn use_color(&self) -> bool { + false + } + + fn set_gc_disabled(&self, _disabled: bool) -> Result<(), ShellError> { + Ok(()) + } + } + pub fn test_polars_plugin_command(command: &impl PluginCommand) -> Result<(), ShellError> { test_polars_plugin_command_with_decls(command, vec![]) } @@ -212,7 +292,7 @@ pub mod test { let id = obj.id(); plugin .cache - .insert(None, id, obj, Span::test_data()) + .insert(TestEngineWrapper {}, id, obj, Span::test_data()) .unwrap(); } } From 4c82a748c1c9dd4881bca646720e117658ac9c41 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:46:56 -0400 Subject: [PATCH 11/38] Do example (#13190) # Description #12056 added support for default and type-checked arguments in `do` closures. This PR adds examples for those features. It also: * Fixes the TODO (a closure parameter that wasn't being used) that was preventing a result from being added * Removes extraneous commas from the descriptions * Adds an example demonstrating multiple positional closure arguments # User-Facing Changes Help examples only # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting --- crates/nu-cmd-lang/src/core_commands/do_.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 8ca3fbac56..4f0bd245d2 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -229,14 +229,24 @@ impl Command for Do { result: None, }, Example { - description: "Run the closure, with a positional parameter", - example: r#"do {|x| 100 + $x } 77"#, + description: "Run the closure with a positional, type-checked parameter", + example: r#"do {|x:int| 100 + $x } 77"#, result: Some(Value::test_int(177)), }, Example { - description: "Run the closure, with input", - example: r#"77 | do {|x| 100 + $in }"#, - result: None, // TODO: returns 177 + description: "Run the closure with pipeline input", + example: r#"77 | do { 100 + $in }"#, + result: Some(Value::test_int(177)), + }, + Example { + description: "Run the closure with a default parameter value", + example: r#"77 | do {|x=100| $x + $in }"#, + result: Some(Value::test_int(177)), + }, + Example { + description: "Run the closure with two positional parameters", + example: r#"do {|x,y| $x + $y } 77 100"#, + result: Some(Value::test_int(177)), }, Example { description: "Run the closure and keep changes to the environment", From 9845d13347d6b26bfc157652ea09027203a461a7 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 21 Jun 2024 03:03:10 -0700 Subject: [PATCH 12/38] fix nu-system build on arm64 FreeBSD (#13196) # Description Fixes #13194 `ki_stat` is supposed to be a `c_char`, but was defined was `i8`. Unfortunately, `c_char` is `u8` on Aarch64 (on all platforms), so this doesn't compile. I fixed it to use `c_char` instead. Double checked whether NetBSD is affected, but the `libc` code defines it as `i8` for some reason (erroneously, really) but that doesn't matter too much. Anyway should be ok there. Confirmed to be working. --- crates/nu-system/src/freebsd.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/nu-system/src/freebsd.rs b/crates/nu-system/src/freebsd.rs index ff0f67dd10..4a556762e4 100644 --- a/crates/nu-system/src/freebsd.rs +++ b/crates/nu-system/src/freebsd.rs @@ -1,6 +1,7 @@ use itertools::{EitherOrBoth, Itertools}; use libc::{ - kinfo_proc, sysctl, CTL_HW, CTL_KERN, KERN_PROC, KERN_PROC_ALL, KERN_PROC_ARGS, TDF_IDLETD, + c_char, kinfo_proc, sysctl, CTL_HW, CTL_KERN, KERN_PROC, KERN_PROC_ALL, KERN_PROC_ARGS, + TDF_IDLETD, }; use std::{ ffi::CStr, @@ -16,7 +17,7 @@ pub struct ProcessInfo { pub ppid: i32, pub name: String, pub argv: Vec, - pub stat: i8, + pub stat: c_char, pub percent_cpu: f64, pub mem_resident: u64, // in bytes pub mem_virtual: u64, // in bytes From dd8f8861ed25058e05fbdaf18b16fc382d4ae751 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 21 Jun 2024 04:17:29 -0700 Subject: [PATCH 13/38] Add shape_glob_interpolation to default_config.nu (#13198) # Description Just missed this during #13089. Adds `shape_glob_interpolation` to the config. This actually isn't really going to be seen at all yet, so I debated whether it's really needed at all. It's only used to highlight the quotes themselves, and we don't have any quoted glob interpolations at the moment. --- crates/nu-utils/src/sample_config/default_config.nu | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 34418feb16..099adcd065 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -48,6 +48,7 @@ let dark_theme = { shape_float: purple_bold # shapes are used to change the cli syntax highlighting shape_garbage: { fg: white bg: red attr: b} + shape_glob_interpolation: cyan_bold shape_globpattern: cyan_bold shape_int: purple_bold shape_internalcall: cyan_bold From 91d44f15c12005744c94bb883fe4092d6acea1d2 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 21 Jun 2024 04:27:09 -0700 Subject: [PATCH 14/38] Allow plugins to report their own version and store it in the registry (#12883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This allows plugins to report their version (and potentially other metadata in the future). The version is shown in `plugin list` and in `version`. The metadata is stored in the registry file, and reflects whatever was retrieved on `plugin add`, not necessarily the running binary. This can help you to diagnose if there's some kind of mismatch with what you expect. We could potentially use this functionality to show a warning or error if a plugin being run does not have the same version as what was in the cache file, suggesting `plugin add` be run again, but I haven't done that at this point. It is optional, and it requires the plugin author to make some code changes if they want to provide it, since I can't automatically determine the version of the calling crate or anything tricky like that to do it. Example: ``` > plugin list | select name version is_running pid ╭───┬────────────────┬─────────┬────────────┬─────╮ │ # │ name │ version │ is_running │ pid │ ├───┼────────────────┼─────────┼────────────┼─────┤ │ 0 │ example │ 0.93.1 │ false │ │ │ 1 │ gstat │ 0.93.1 │ false │ │ │ 2 │ inc │ 0.93.1 │ false │ │ │ 3 │ python_example │ 0.1.0 │ false │ │ ╰───┴────────────────┴─────────┴────────────┴─────╯ ``` cc @maxim-uvarov (he asked for it) # User-Facing Changes - `plugin list` gets a `version` column - `version` shows plugin versions when available - plugin authors *should* add `fn metadata()` to their `impl Plugin`, but don't have to # Tests + Formatting Tested the low level stuff and also the `plugin list` column. # After Submitting - [ ] update plugin guide docs - [ ] update plugin protocol docs (`Metadata` call & response) - [ ] update plugin template (`fn metadata()` should be easy) - [ ] release notes --- crates/nu-cli/src/config_files.rs | 5 ++- .../nu-cmd-lang/src/core_commands/version.rs | 11 +++++- .../nu-cmd-plugin/src/commands/plugin/add.rs | 5 ++- .../nu-cmd-plugin/src/commands/plugin/list.rs | 9 +++++ crates/nu-parser/src/parse_keywords.rs | 33 ++++++++++------ crates/nu-plugin-engine/src/init.rs | 5 ++- crates/nu-plugin-engine/src/interface/mod.rs | 17 ++++++++- .../nu-plugin-engine/src/interface/tests.rs | 21 +++++++++- crates/nu-plugin-engine/src/persistent.rs | 15 +++++++- crates/nu-plugin-protocol/src/lib.rs | 7 +++- .../src/fake_persistent_plugin.rs | 8 +++- crates/nu-plugin-test-support/src/lib.rs | 4 ++ .../tests/custom_value/mod.rs | 4 ++ .../nu-plugin-test-support/tests/hello/mod.rs | 4 ++ .../tests/lowercase/mod.rs | 4 ++ crates/nu-plugin/src/lib.rs | 4 ++ crates/nu-plugin/src/plugin/command.rs | 6 +++ crates/nu-plugin/src/plugin/interface/mod.rs | 21 ++++++++-- .../nu-plugin/src/plugin/interface/tests.rs | 20 ++++++++++ crates/nu-plugin/src/plugin/mod.rs | 31 ++++++++++++++- crates/nu-protocol/src/plugin/metadata.rs | 38 +++++++++++++++++++ crates/nu-protocol/src/plugin/mod.rs | 2 + crates/nu-protocol/src/plugin/registered.rs | 8 +++- .../src/plugin/registry_file/mod.rs | 10 +++-- .../src/plugin/registry_file/tests.rs | 9 ++++- crates/nu_plugin_custom_values/src/main.rs | 4 ++ crates/nu_plugin_example/src/lib.rs | 4 ++ crates/nu_plugin_formats/src/lib.rs | 4 ++ crates/nu_plugin_gstat/src/nu/mod.rs | 4 ++ crates/nu_plugin_inc/src/nu/mod.rs | 4 ++ .../nu_plugin_nu_example.nu | 8 ++++ crates/nu_plugin_polars/src/lib.rs | 4 ++ .../nu_plugin_python_example.py | 9 ++++- crates/nu_plugin_query/src/query.rs | 4 ++ crates/nu_plugin_stress_internals/src/main.rs | 16 +++++++- src/main.rs | 12 ++++-- tests/plugin_persistence/mod.rs | 11 ++++++ tests/plugins/registry_file.rs | 21 ++++++---- 38 files changed, 360 insertions(+), 46 deletions(-) create mode 100644 crates/nu-protocol/src/plugin/metadata.rs diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index ec7ad2f412..6cf4735c46 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -344,7 +344,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) - name: identity.name().to_owned(), filename: identity.filename().to_owned(), shell: identity.shell().map(|p| p.to_owned()), - data: PluginRegistryItemData::Valid { commands }, + data: PluginRegistryItemData::Valid { + metadata: Default::default(), + commands, + }, }); } diff --git a/crates/nu-cmd-lang/src/core_commands/version.rs b/crates/nu-cmd-lang/src/core_commands/version.rs index 77283105a9..5491db65fc 100644 --- a/crates/nu-cmd-lang/src/core_commands/version.rs +++ b/crates/nu-cmd-lang/src/core_commands/version.rs @@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result>(); record.push( diff --git a/crates/nu-cmd-plugin/src/commands/plugin/add.rs b/crates/nu-cmd-plugin/src/commands/plugin/add.rs index 225941db01..14f3541168 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/add.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/add.rs @@ -118,11 +118,12 @@ apparent the next time `nu` is next launched with that plugin registry file. }, )); let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?; + let metadata = interface.get_metadata()?; let commands = interface.get_signature()?; modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| { - // Update the file with the received signatures - let item = PluginRegistryItem::new(plugin.identity(), commands); + // Update the file with the received metadata and signatures + let item = PluginRegistryItem::new(plugin.identity(), metadata, commands); contents.upsert_plugin(item); Ok(()) })?; diff --git a/crates/nu-cmd-plugin/src/commands/plugin/list.rs b/crates/nu-cmd-plugin/src/commands/plugin/list.rs index 6b715a0001..030a3341d6 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/list.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/list.rs @@ -16,6 +16,7 @@ impl Command for PluginList { Type::Table( [ ("name".into(), Type::String), + ("version".into(), Type::String), ("is_running".into(), Type::Bool), ("pid".into(), Type::Int), ("filename".into(), Type::String), @@ -43,6 +44,7 @@ impl Command for PluginList { description: "List installed plugins.", result: Some(Value::test_list(vec![Value::test_record(record! { "name" => Value::test_string("inc"), + "version" => Value::test_string(env!("CARGO_PKG_VERSION")), "is_running" => Value::test_bool(true), "pid" => Value::test_int(106480), "filename" => if cfg!(windows) { @@ -98,8 +100,15 @@ impl Command for PluginList { .map(|s| Value::string(s.to_string_lossy(), head)) .unwrap_or(Value::nothing(head)); + let metadata = plugin.metadata(); + let version = metadata + .and_then(|m| m.version) + .map(|s| Value::string(s, head)) + .unwrap_or(Value::nothing(head)); + let record = record! { "name" => Value::string(plugin.identity().name(), head), + "version" => version, "is_running" => Value::bool(plugin.is_running(), head), "pid" => pid, "filename" => Value::string(plugin.identity().filename().to_string_lossy(), head), diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 2849b4e39c..673a59fb05 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -3740,28 +3740,37 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm ) })?; - let signatures = plugin + let metadata_and_signatures = plugin .clone() .get(get_envs) - .and_then(|p| p.get_signature()) + .and_then(|p| { + let meta = p.get_metadata()?; + let sigs = p.get_signature()?; + Ok((meta, sigs)) + }) .map_err(|err| { - log::warn!("Error getting signatures: {err:?}"); + log::warn!("Error getting metadata and signatures: {err:?}"); ParseError::LabeledError( - "Error getting signatures".into(), + "Error getting metadata and signatures".into(), err.to_string(), spans[0], ) }); - if let Ok(ref signatures) = signatures { - // Add the loaded plugin to the delta - working_set.update_plugin_registry(PluginRegistryItem::new( - &identity, - signatures.clone(), - )); + match metadata_and_signatures { + Ok((meta, sigs)) => { + // Set the metadata on the plugin + plugin.set_metadata(Some(meta.clone())); + // Add the loaded plugin to the delta + working_set.update_plugin_registry(PluginRegistryItem::new( + &identity, + meta, + sigs.clone(), + )); + Ok(sigs) + } + Err(err) => Err(err), } - - signatures }, |sig| sig.map(|sig| vec![sig]), )?; diff --git a/crates/nu-plugin-engine/src/init.rs b/crates/nu-plugin-engine/src/init.rs index 198a01cd1c..44fa4f031f 100644 --- a/crates/nu-plugin-engine/src/init.rs +++ b/crates/nu-plugin-engine/src/init.rs @@ -252,7 +252,7 @@ pub fn load_plugin_registry_item( })?; match &plugin.data { - PluginRegistryItemData::Valid { commands } => { + PluginRegistryItemData::Valid { metadata, commands } => { let plugin = add_plugin_to_working_set(working_set, &identity)?; // Ensure that the plugin is reset. We're going to load new signatures, so we want to @@ -260,6 +260,9 @@ pub fn load_plugin_registry_item( // doesn't. plugin.reset()?; + // Set the plugin metadata from the file + plugin.set_metadata(Some(metadata.clone())); + // Create the declarations from the commands for signature in commands { let decl = PluginDeclaration::new(plugin.clone(), signature.clone()); diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index adab9dc68d..9a4c72d2c1 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -11,8 +11,8 @@ use nu_plugin_protocol::{ PluginOutput, ProtocolInfo, StreamId, StreamMessage, }; use nu_protocol::{ - ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginSignature, ShellError, Span, - Spanned, Value, + ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature, + ShellError, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap}, @@ -716,6 +716,7 @@ impl PluginInterface { // Convert the call into one with a header and handle the stream, if necessary let (call, writer) = match call { + PluginCall::Metadata => (PluginCall::Metadata, Default::default()), PluginCall::Signature => (PluginCall::Signature, Default::default()), PluginCall::CustomValueOp(value, op) => { (PluginCall::CustomValueOp(value, op), Default::default()) @@ -913,6 +914,17 @@ impl PluginInterface { self.receive_plugin_call_response(result.receiver, context, result.state) } + /// Get the metadata from the plugin. + pub fn get_metadata(&self) -> Result { + match self.plugin_call(PluginCall::Metadata, None)? { + PluginCallResponse::Metadata(meta) => Ok(meta), + PluginCallResponse::Error(err) => Err(err.into()), + _ => Err(ShellError::PluginFailedToDecode { + msg: "Received unexpected response to plugin Metadata call".into(), + }), + } + } + /// Get the command signatures from the plugin. pub fn get_signature(&self) -> Result, ShellError> { match self.plugin_call(PluginCall::Signature, None)? { @@ -1206,6 +1218,7 @@ impl CurrentCallState { source: &PluginSource, ) -> Result<(), ShellError> { match call { + PluginCall::Metadata => Ok(()), PluginCall::Signature => Ok(()), PluginCall::Run(CallInfo { call, .. }) => self.prepare_call_args(call, source), PluginCall::CustomValueOp(_, op) => { diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index e718886b3b..5665beb92b 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -18,7 +18,7 @@ use nu_protocol::{ ast::{Math, Operator}, engine::Closure, ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData, - PluginSignature, ShellError, Span, Spanned, Value, + PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; use std::{ @@ -1019,6 +1019,25 @@ fn start_fake_plugin_call_responder( .expect("failed to spawn thread"); } +#[test] +fn interface_get_metadata() -> Result<(), ShellError> { + let test = TestCase::new(); + let manager = test.plugin("test"); + let interface = manager.get_interface(); + + start_fake_plugin_call_responder(manager, 1, |_| { + vec![ReceivedPluginCallMessage::Response( + PluginCallResponse::Metadata(PluginMetadata::new().with_version("test")), + )] + }); + + let metadata = interface.get_metadata()?; + + assert_eq!(Some("test"), metadata.version.as_deref()); + assert!(test.has_unconsumed_write()); + Ok(()) +} + #[test] fn interface_get_signature() -> Result<(), ShellError> { let test = TestCase::new(); diff --git a/crates/nu-plugin-engine/src/persistent.rs b/crates/nu-plugin-engine/src/persistent.rs index 5f01c70ca7..6a87aa1e6b 100644 --- a/crates/nu-plugin-engine/src/persistent.rs +++ b/crates/nu-plugin-engine/src/persistent.rs @@ -7,7 +7,7 @@ use super::{PluginInterface, PluginSource}; use nu_plugin_core::CommunicationMode; use nu_protocol::{ engine::{EngineState, Stack}, - PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError, + PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError, }; use std::{ collections::HashMap, @@ -31,6 +31,8 @@ pub struct PersistentPlugin { struct MutableState { /// Reference to the plugin if running running: Option, + /// Metadata for the plugin, e.g. version. + metadata: Option, /// Plugin's preferred communication mode (if known) preferred_mode: Option, /// Garbage collector config @@ -59,6 +61,7 @@ impl PersistentPlugin { identity, mutable: Mutex::new(MutableState { running: None, + metadata: None, preferred_mode: None, gc_config, }), @@ -268,6 +271,16 @@ impl RegisteredPlugin for PersistentPlugin { self.stop_internal(true) } + fn metadata(&self) -> Option { + self.mutable.lock().ok().and_then(|m| m.metadata.clone()) + } + + fn set_metadata(&self, metadata: Option) { + if let Ok(mut mutable) = self.mutable.lock() { + mutable.metadata = metadata; + } + } + fn set_gc_config(&self, gc_config: &PluginGcConfig) { if let Ok(mut mutable) = self.mutable.lock() { // Save the new config for future calls diff --git a/crates/nu-plugin-protocol/src/lib.rs b/crates/nu-plugin-protocol/src/lib.rs index db19ee02f6..2f582c3009 100644 --- a/crates/nu-plugin-protocol/src/lib.rs +++ b/crates/nu-plugin-protocol/src/lib.rs @@ -23,7 +23,7 @@ pub mod test_util; use nu_protocol::{ ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData, - PluginSignature, ShellError, Span, Spanned, Value, + PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -119,6 +119,7 @@ pub struct ByteStreamInfo { /// Calls that a plugin can execute. The type parameter determines the input type. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum PluginCall { + Metadata, Signature, Run(CallInfo), CustomValueOp(Spanned, CustomValueOp), @@ -132,6 +133,7 @@ impl PluginCall { f: impl FnOnce(D) -> Result, ) -> Result, ShellError> { Ok(match self { + PluginCall::Metadata => PluginCall::Metadata, PluginCall::Signature => PluginCall::Signature, PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?), PluginCall::CustomValueOp(custom_value, op) => { @@ -143,6 +145,7 @@ impl PluginCall { /// The span associated with the call. pub fn span(&self) -> Option { match self { + PluginCall::Metadata => None, PluginCall::Signature => None, PluginCall::Run(CallInfo { call, .. }) => Some(call.head), PluginCall::CustomValueOp(val, _) => Some(val.span), @@ -309,6 +312,7 @@ pub enum StreamMessage { #[derive(Serialize, Deserialize, Debug, Clone)] pub enum PluginCallResponse { Error(LabeledError), + Metadata(PluginMetadata), Signature(Vec), Ordering(Option), PipelineData(D), @@ -323,6 +327,7 @@ impl PluginCallResponse { ) -> Result, ShellError> { Ok(match self { PluginCallResponse::Error(err) => PluginCallResponse::Error(err), + PluginCallResponse::Metadata(meta) => PluginCallResponse::Metadata(meta), PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs), PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering), PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?), diff --git a/crates/nu-plugin-test-support/src/fake_persistent_plugin.rs b/crates/nu-plugin-test-support/src/fake_persistent_plugin.rs index 617e55c3d1..e316e85147 100644 --- a/crates/nu-plugin-test-support/src/fake_persistent_plugin.rs +++ b/crates/nu-plugin-test-support/src/fake_persistent_plugin.rs @@ -6,7 +6,7 @@ use std::{ use nu_plugin_engine::{GetPlugin, PluginInterface}; use nu_protocol::{ engine::{EngineState, Stack}, - PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError, + PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError, }; pub struct FakePersistentPlugin { @@ -42,6 +42,12 @@ impl RegisteredPlugin for FakePersistentPlugin { None } + fn metadata(&self) -> Option { + None + } + + fn set_metadata(&self, _metadata: Option) {} + fn set_gc_config(&self, _gc_config: &PluginGcConfig) { // We don't have a GC } diff --git a/crates/nu-plugin-test-support/src/lib.rs b/crates/nu-plugin-test-support/src/lib.rs index caa7cbac1a..a53b353981 100644 --- a/crates/nu-plugin-test-support/src/lib.rs +++ b/crates/nu-plugin-test-support/src/lib.rs @@ -66,6 +66,10 @@ //! } //! //! impl Plugin for LowercasePlugin { +//! fn version(&self) -> String { +//! env!("CARGO_PKG_VERSION").into() +//! } +//! //! fn commands(&self) -> Vec>> { //! vec![Box::new(Lowercase)] //! } diff --git a/crates/nu-plugin-test-support/tests/custom_value/mod.rs b/crates/nu-plugin-test-support/tests/custom_value/mod.rs index f703a92e33..888f05c1cb 100644 --- a/crates/nu-plugin-test-support/tests/custom_value/mod.rs +++ b/crates/nu-plugin-test-support/tests/custom_value/mod.rs @@ -53,6 +53,10 @@ struct IntoU32; struct IntoIntFromU32; impl Plugin for CustomU32Plugin { + fn version(&self) -> String { + "0.0.0".into() + } + fn commands(&self) -> Vec>> { vec![Box::new(IntoU32), Box::new(IntoIntFromU32)] } diff --git a/crates/nu-plugin-test-support/tests/hello/mod.rs b/crates/nu-plugin-test-support/tests/hello/mod.rs index 424940f156..a222637a76 100644 --- a/crates/nu-plugin-test-support/tests/hello/mod.rs +++ b/crates/nu-plugin-test-support/tests/hello/mod.rs @@ -8,6 +8,10 @@ struct HelloPlugin; struct Hello; impl Plugin for HelloPlugin { + fn version(&self) -> String { + "0.0.0".into() + } + fn commands(&self) -> Vec>> { vec![Box::new(Hello)] } diff --git a/crates/nu-plugin-test-support/tests/lowercase/mod.rs b/crates/nu-plugin-test-support/tests/lowercase/mod.rs index 0072a08aa2..50271a8cc2 100644 --- a/crates/nu-plugin-test-support/tests/lowercase/mod.rs +++ b/crates/nu-plugin-test-support/tests/lowercase/mod.rs @@ -59,6 +59,10 @@ impl PluginCommand for Lowercase { } impl Plugin for LowercasePlugin { + fn version(&self) -> String { + "0.0.0".into() + } + fn commands(&self) -> Vec>> { vec![Box::new(Lowercase)] } diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index 915c8d36fe..4c1a033a02 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -24,6 +24,10 @@ //! struct MyCommand; //! //! impl Plugin for MyPlugin { +//! fn version(&self) -> String { +//! env!("CARGO_PKG_VERSION").into() +//! } +//! //! fn commands(&self) -> Vec>> { //! vec![Box::new(MyCommand)] //! } diff --git a/crates/nu-plugin/src/plugin/command.rs b/crates/nu-plugin/src/plugin/command.rs index 5def950b0b..7ccedfd4e5 100644 --- a/crates/nu-plugin/src/plugin/command.rs +++ b/crates/nu-plugin/src/plugin/command.rs @@ -60,6 +60,9 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; /// } /// /// # impl Plugin for LowercasePlugin { +/// # fn version(&self) -> String { +/// # "0.0.0".into() +/// # } /// # fn commands(&self) -> Vec>> { /// # vec![Box::new(Lowercase)] /// # } @@ -195,6 +198,9 @@ pub trait PluginCommand: Sync { /// } /// /// # impl Plugin for HelloPlugin { +/// # fn version(&self) -> String { +/// # "0.0.0".into() +/// # } /// # fn commands(&self) -> Vec>> { /// # vec![Box::new(Hello)] /// # } diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index e3e9679471..fc9c9e85b3 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -11,8 +11,8 @@ use nu_plugin_protocol::{ ProtocolInfo, }; use nu_protocol::{ - engine::Closure, Config, LabeledError, PipelineData, PluginSignature, ShellError, Span, - Spanned, Value, + engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, + ShellError, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap, HashMap}, @@ -29,6 +29,9 @@ use std::{ #[derive(Debug)] #[doc(hidden)] pub enum ReceivedPluginCall { + Metadata { + engine: EngineInterface, + }, Signature { engine: EngineInterface, }, @@ -280,8 +283,11 @@ impl InterfaceManager for EngineInterfaceManager { } }; match call { - // We just let the receiver handle it rather than trying to store signature here - // or something + // Ask the plugin for metadata + PluginCall::Metadata => { + self.send_plugin_call(ReceivedPluginCall::Metadata { engine: interface }) + } + // Ask the plugin for signatures PluginCall::Signature => { self.send_plugin_call(ReceivedPluginCall::Signature { engine: interface }) } @@ -416,6 +422,13 @@ impl EngineInterface { } } + /// Write a call response of plugin metadata. + pub(crate) fn write_metadata(&self, metadata: PluginMetadata) -> Result<(), ShellError> { + let response = PluginCallResponse::Metadata(metadata); + self.write(PluginOutput::CallResponse(self.context()?, response))?; + self.flush() + } + /// Write a call response of plugin signatures. /// /// Any custom values in the examples will be rendered using `to_base_value()`. diff --git a/crates/nu-plugin/src/plugin/interface/tests.rs b/crates/nu-plugin/src/plugin/interface/tests.rs index 6c3dfdf6c9..b195b43197 100644 --- a/crates/nu-plugin/src/plugin/interface/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/tests.rs @@ -322,6 +322,26 @@ fn manager_consume_goodbye_closes_plugin_call_channel() -> Result<(), ShellError Ok(()) } +#[test] +fn manager_consume_call_metadata_forwards_to_receiver_with_context() -> Result<(), ShellError> { + let mut manager = TestCase::new().engine(); + set_default_protocol_info(&mut manager)?; + + let rx = manager + .take_plugin_call_receiver() + .expect("couldn't take receiver"); + + manager.consume(PluginInput::Call(0, PluginCall::Metadata))?; + + match rx.try_recv().expect("call was not forwarded to receiver") { + ReceivedPluginCall::Metadata { engine } => { + assert_eq!(Some(0), engine.context); + Ok(()) + } + call => panic!("wrong call type: {call:?}"), + } +} + #[test] fn manager_consume_call_signature_forwards_to_receiver_with_context() -> Result<(), ShellError> { let mut manager = TestCase::new().engine(); diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 85283aadd0..997381f97b 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -16,7 +16,8 @@ use nu_plugin_core::{ }; use nu_plugin_protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput}; use nu_protocol::{ - ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, ShellError, Spanned, Value, + ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginMetadata, + ShellError, Spanned, Value, }; use thiserror::Error; @@ -52,6 +53,10 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384; /// struct Hello; /// /// impl Plugin for HelloPlugin { +/// fn version(&self) -> String { +/// env!("CARGO_PKG_VERSION").into() +/// } +/// /// fn commands(&self) -> Vec>> { /// vec![Box::new(Hello)] /// } @@ -89,6 +94,23 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384; /// # } /// ``` pub trait Plugin: Sync { + /// The version of the plugin. + /// + /// The recommended implementation, which will use the version from your crate's `Cargo.toml` + /// file: + /// + /// ```no_run + /// # use nu_plugin::{Plugin, PluginCommand}; + /// # struct MyPlugin; + /// # impl Plugin for MyPlugin { + /// fn version(&self) -> String { + /// env!("CARGO_PKG_VERSION").into() + /// } + /// # fn commands(&self) -> Vec>> { vec![] } + /// # } + /// ``` + fn version(&self) -> String; + /// The commands supported by the plugin /// /// Each [`PluginCommand`] contains both the signature of the command and the functionality it @@ -216,6 +238,7 @@ pub trait Plugin: Sync { /// # struct MyPlugin; /// # impl MyPlugin { fn new() -> Self { Self }} /// # impl Plugin for MyPlugin { +/// # fn version(&self) -> String { "0.0.0".into() } /// # fn commands(&self) -> Vec>> {todo!();} /// # } /// fn main() { @@ -504,6 +527,12 @@ where } match plugin_call { + // Send metadata back to nushell so it can be stored with the plugin signatures + ReceivedPluginCall::Metadata { engine } => { + engine + .write_metadata(PluginMetadata::new().with_version(plugin.version())) + .try_to_report(&engine)?; + } // Sending the signature back to nushell to create the declaration definition ReceivedPluginCall::Signature { engine } => { let sigs = commands diff --git a/crates/nu-protocol/src/plugin/metadata.rs b/crates/nu-protocol/src/plugin/metadata.rs new file mode 100644 index 0000000000..d2fab7a89b --- /dev/null +++ b/crates/nu-protocol/src/plugin/metadata.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Serialize}; + +/// Metadata about the installed plugin. This is cached in the registry file along with the +/// signatures. None of the metadata fields are required, and more may be added in the future. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[non_exhaustive] +pub struct PluginMetadata { + /// The version of the plugin itself, as self-reported. + pub version: Option, +} + +impl PluginMetadata { + /// Create empty metadata. + pub const fn new() -> PluginMetadata { + PluginMetadata { version: None } + } + + /// Set the version of the plugin on the metadata. A suggested way to construct this is: + /// + /// ```no_run + /// # use nu_protocol::PluginMetadata; + /// # fn example() -> PluginMetadata { + /// PluginMetadata::new().with_version(env!("CARGO_PKG_VERSION")) + /// # } + /// ``` + /// + /// which will use the version of your plugin's crate from its `Cargo.toml` file. + pub fn with_version(mut self, version: impl Into) -> Self { + self.version = Some(version.into()); + self + } +} + +impl Default for PluginMetadata { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/nu-protocol/src/plugin/mod.rs b/crates/nu-protocol/src/plugin/mod.rs index b266f8ebac..1678c9c40a 100644 --- a/crates/nu-protocol/src/plugin/mod.rs +++ b/crates/nu-protocol/src/plugin/mod.rs @@ -1,9 +1,11 @@ mod identity; +mod metadata; mod registered; mod registry_file; mod signature; pub use identity::*; +pub use metadata::*; pub use registered::*; pub use registry_file::*; pub use signature::*; diff --git a/crates/nu-protocol/src/plugin/registered.rs b/crates/nu-protocol/src/plugin/registered.rs index 46d65b41d1..abd75b4dc6 100644 --- a/crates/nu-protocol/src/plugin/registered.rs +++ b/crates/nu-protocol/src/plugin/registered.rs @@ -1,6 +1,6 @@ use std::{any::Any, sync::Arc}; -use crate::{PluginGcConfig, PluginIdentity, ShellError}; +use crate::{PluginGcConfig, PluginIdentity, PluginMetadata, ShellError}; /// Trait for plugins registered in the [`EngineState`](crate::engine::EngineState). pub trait RegisteredPlugin: Send + Sync { @@ -13,6 +13,12 @@ pub trait RegisteredPlugin: Send + Sync { /// Process ID of the plugin executable, if running. fn pid(&self) -> Option; + /// Get metadata for the plugin, if set. + fn metadata(&self) -> Option; + + /// Set metadata for the plugin. + fn set_metadata(&self, metadata: Option); + /// Set garbage collection config for the plugin. fn set_gc_config(&self, gc_config: &PluginGcConfig); diff --git a/crates/nu-protocol/src/plugin/registry_file/mod.rs b/crates/nu-protocol/src/plugin/registry_file/mod.rs index dcaba26b90..17adec1f4e 100644 --- a/crates/nu-protocol/src/plugin/registry_file/mod.rs +++ b/crates/nu-protocol/src/plugin/registry_file/mod.rs @@ -5,7 +5,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{PluginIdentity, PluginSignature, ShellError, Span}; +use crate::{PluginIdentity, PluginMetadata, PluginSignature, ShellError, Span}; // This has a big impact on performance const BUFFER_SIZE: usize = 65536; @@ -121,9 +121,10 @@ pub struct PluginRegistryItem { } impl PluginRegistryItem { - /// Create a [`PluginRegistryItem`] from an identity and signatures. + /// Create a [`PluginRegistryItem`] from an identity, metadata, and signatures. pub fn new( identity: &PluginIdentity, + metadata: PluginMetadata, mut commands: Vec, ) -> PluginRegistryItem { // Sort the commands for consistency @@ -133,7 +134,7 @@ impl PluginRegistryItem { name: identity.name().to_owned(), filename: identity.filename().to_owned(), shell: identity.shell().map(|p| p.to_owned()), - data: PluginRegistryItemData::Valid { commands }, + data: PluginRegistryItemData::Valid { metadata, commands }, } } } @@ -144,6 +145,9 @@ impl PluginRegistryItem { #[serde(untagged)] pub enum PluginRegistryItemData { Valid { + /// Metadata for the plugin, including its version. + #[serde(default)] + metadata: PluginMetadata, /// Signatures and examples for each command provided by the plugin. commands: Vec, }, diff --git a/crates/nu-protocol/src/plugin/registry_file/tests.rs b/crates/nu-protocol/src/plugin/registry_file/tests.rs index 0d34ecca1c..e264e4463d 100644 --- a/crates/nu-protocol/src/plugin/registry_file/tests.rs +++ b/crates/nu-protocol/src/plugin/registry_file/tests.rs @@ -1,6 +1,7 @@ use super::{PluginRegistryFile, PluginRegistryItem, PluginRegistryItemData}; use crate::{ - Category, PluginExample, PluginSignature, ShellError, Signature, SyntaxShape, Type, Value, + Category, PluginExample, PluginMetadata, PluginSignature, ShellError, Signature, SyntaxShape, + Type, Value, }; use pretty_assertions::assert_eq; use std::io::Cursor; @@ -11,6 +12,9 @@ fn foo_plugin() -> PluginRegistryItem { filename: "/path/to/nu_plugin_foo".into(), shell: None, data: PluginRegistryItemData::Valid { + metadata: PluginMetadata { + version: Some("0.1.0".into()), + }, commands: vec![PluginSignature { sig: Signature::new("foo") .input_output_type(Type::Int, Type::List(Box::new(Type::Int))) @@ -36,6 +40,9 @@ fn bar_plugin() -> PluginRegistryItem { filename: "/path/to/nu_plugin_bar".into(), shell: None, data: PluginRegistryItemData::Valid { + metadata: PluginMetadata { + version: Some("0.2.0".into()), + }, commands: vec![PluginSignature { sig: Signature::new("bar") .usage("overwrites files with random data") diff --git a/crates/nu_plugin_custom_values/src/main.rs b/crates/nu_plugin_custom_values/src/main.rs index 9ab69135fb..65cc0b500f 100644 --- a/crates/nu_plugin_custom_values/src/main.rs +++ b/crates/nu_plugin_custom_values/src/main.rs @@ -42,6 +42,10 @@ impl CustomValuePlugin { } impl Plugin for CustomValuePlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { vec![ Box::new(Generate), diff --git a/crates/nu_plugin_example/src/lib.rs b/crates/nu_plugin_example/src/lib.rs index 182bc85121..acbebf9b39 100644 --- a/crates/nu_plugin_example/src/lib.rs +++ b/crates/nu_plugin_example/src/lib.rs @@ -7,6 +7,10 @@ pub use commands::*; pub use example::ExamplePlugin; impl Plugin for ExamplePlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { // This is a list of all of the commands you would like Nu to register when your plugin is // loaded. diff --git a/crates/nu_plugin_formats/src/lib.rs b/crates/nu_plugin_formats/src/lib.rs index 748d29cd21..2ae24a4971 100644 --- a/crates/nu_plugin_formats/src/lib.rs +++ b/crates/nu_plugin_formats/src/lib.rs @@ -10,6 +10,10 @@ pub use from::vcf::FromVcf; pub struct FromCmds; impl Plugin for FromCmds { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { vec![ Box::new(FromEml), diff --git a/crates/nu_plugin_gstat/src/nu/mod.rs b/crates/nu_plugin_gstat/src/nu/mod.rs index 223e5a49b5..f44894085d 100644 --- a/crates/nu_plugin_gstat/src/nu/mod.rs +++ b/crates/nu_plugin_gstat/src/nu/mod.rs @@ -5,6 +5,10 @@ use nu_protocol::{Category, LabeledError, Signature, Spanned, SyntaxShape, Value pub struct GStatPlugin; impl Plugin for GStatPlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { vec![Box::new(GStat)] } diff --git a/crates/nu_plugin_inc/src/nu/mod.rs b/crates/nu_plugin_inc/src/nu/mod.rs index 148f1a7002..b7d9c8960f 100644 --- a/crates/nu_plugin_inc/src/nu/mod.rs +++ b/crates/nu_plugin_inc/src/nu/mod.rs @@ -5,6 +5,10 @@ use nu_protocol::{ast::CellPath, LabeledError, Signature, SyntaxShape, Value}; pub struct IncPlugin; impl Plugin for IncPlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { vec![Box::new(Inc::new())] } diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index 028e9735fd..4bfc066815 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -7,6 +7,7 @@ # language without adding any extra dependencies to our tests. const NUSHELL_VERSION = "0.94.3" +const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { if ($stdio) { @@ -229,6 +230,13 @@ def handle_input []: any -> nothing { } { Call: [$id, $plugin_call] } => { match $plugin_call { + "Metadata" => { + write_response $id { + Metadata: { + version: $PLUGIN_VERSION + } + } + } "Signature" => { write_response $id { Signature: $SIGNATURES } } diff --git a/crates/nu_plugin_polars/src/lib.rs b/crates/nu_plugin_polars/src/lib.rs index 76da2eb039..9fc9a7ab2d 100644 --- a/crates/nu_plugin_polars/src/lib.rs +++ b/crates/nu_plugin_polars/src/lib.rs @@ -99,6 +99,10 @@ pub struct PolarsPlugin { } impl Plugin for PolarsPlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { let mut commands: Vec>> = vec![Box::new(PolarsCmd)]; commands.append(&mut eager_commands()); diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index 3db89a0afc..80715abf11 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -28,6 +28,7 @@ import json NUSHELL_VERSION = "0.94.3" +PLUGIN_VERSION = "0.1.0" # bump if you change commands! def signatures(): @@ -228,7 +229,13 @@ def handle_input(input): exit(0) elif "Call" in input: [id, plugin_call] = input["Call"] - if plugin_call == "Signature": + if plugin_call == "Metadata": + write_response(id, { + "Metadata": { + "version": PLUGIN_VERSION, + } + }) + elif plugin_call == "Signature": write_response(id, signatures()) elif "Run" in plugin_call: process_call(id, plugin_call["Run"]) diff --git a/crates/nu_plugin_query/src/query.rs b/crates/nu_plugin_query/src/query.rs index 1e143068c4..c22339ab4a 100644 --- a/crates/nu_plugin_query/src/query.rs +++ b/crates/nu_plugin_query/src/query.rs @@ -16,6 +16,10 @@ impl Query { } impl Plugin for Query { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + fn commands(&self) -> Vec>> { vec![ Box::new(QueryCommand), diff --git a/crates/nu_plugin_stress_internals/src/main.rs b/crates/nu_plugin_stress_internals/src/main.rs index bdbe5c8943..96023b44b7 100644 --- a/crates/nu_plugin_stress_internals/src/main.rs +++ b/crates/nu_plugin_stress_internals/src/main.rs @@ -136,7 +136,21 @@ fn handle_message( ) -> Result<(), Box> { if let Some(plugin_call) = message.get("Call") { let (id, plugin_call) = (&plugin_call[0], &plugin_call[1]); - if plugin_call.as_str() == Some("Signature") { + if plugin_call.as_str() == Some("Metadata") { + write( + output, + &json!({ + "CallResponse": [ + id, + { + "Metadata": { + "version": env!("CARGO_PKG_VERSION"), + } + } + ] + }), + ) + } else if plugin_call.as_str() == Some("Signature") { write( output, &json!({ diff --git a/src/main.rs b/src/main.rs index 41d5534bb0..c74fd7641d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -400,7 +400,7 @@ fn main() -> Result<()> { #[cfg(feature = "plugin")] if let Some(plugins) = &parsed_nu_cli_args.plugins { use nu_plugin_engine::{GetPlugin, PluginDeclaration}; - use nu_protocol::{engine::StateWorkingSet, ErrSpan, PluginIdentity}; + use nu_protocol::{engine::StateWorkingSet, ErrSpan, PluginIdentity, RegisteredPlugin}; // Load any plugins specified with --plugins start_time = std::time::Instant::now(); @@ -419,8 +419,14 @@ fn main() -> Result<()> { // Create the plugin and add it to the working set let plugin = nu_plugin_engine::add_plugin_to_working_set(&mut working_set, &identity)?; - // Spawn the plugin to get its signatures, and then add the commands to the working set - for signature in plugin.clone().get_plugin(None)?.get_signature()? { + // Spawn the plugin to get the metadata and signatures + let interface = plugin.clone().get_plugin(None)?; + + // Set its metadata + plugin.set_metadata(Some(interface.get_metadata()?)); + + // Add the commands from the signature to the working set + for signature in interface.get_signature()? { let decl = PluginDeclaration::new(plugin.clone(), signature); working_set.add_decl(Box::new(decl)); } diff --git a/tests/plugin_persistence/mod.rs b/tests/plugin_persistence/mod.rs index d9925f6bbf..6729c657c7 100644 --- a/tests/plugin_persistence/mod.rs +++ b/tests/plugin_persistence/mod.rs @@ -16,6 +16,17 @@ fn plugin_list_shows_installed_plugins() { assert!(out.status.success()); } +#[test] +fn plugin_list_shows_installed_plugin_version() { + let out = nu_with_plugins!( + cwd: ".", + plugin: ("nu_plugin_inc"), + r#"(plugin list).version.0"# + ); + assert_eq!(env!("CARGO_PKG_VERSION"), out.out); + assert!(out.status.success()); +} + #[test] fn plugin_keeps_running_after_calling_it() { let out = nu_with_plugins!( diff --git a/tests/plugins/registry_file.rs b/tests/plugins/registry_file.rs index 3f7da6dd03..c0f0fa0724 100644 --- a/tests/plugins/registry_file.rs +++ b/tests/plugins/registry_file.rs @@ -18,6 +18,13 @@ fn example_plugin_path() -> PathBuf { .expect("nu_plugin_example not found") } +fn valid_plugin_item_data() -> PluginRegistryItemData { + PluginRegistryItemData::Valid { + metadata: Default::default(), + commands: vec![], + } +} + #[test] fn plugin_add_then_restart_nu() { let result = nu_with_plugins!( @@ -149,7 +156,7 @@ fn plugin_rm_then_restart_nu() { name: "example".into(), filename: example_plugin_path, shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents.upsert_plugin(PluginRegistryItem { @@ -157,7 +164,7 @@ fn plugin_rm_then_restart_nu() { // this doesn't exist, but it should be ok filename: dirs.test().join("nu_plugin_foo"), shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents @@ -225,7 +232,7 @@ fn plugin_rm_from_custom_path() { name: "example".into(), filename: example_plugin_path, shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents.upsert_plugin(PluginRegistryItem { @@ -233,7 +240,7 @@ fn plugin_rm_from_custom_path() { // this doesn't exist, but it should be ok filename: dirs.test().join("nu_plugin_foo"), shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents @@ -273,7 +280,7 @@ fn plugin_rm_using_filename() { name: "example".into(), filename: example_plugin_path.clone(), shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents.upsert_plugin(PluginRegistryItem { @@ -281,7 +288,7 @@ fn plugin_rm_using_filename() { // this doesn't exist, but it should be ok filename: dirs.test().join("nu_plugin_foo"), shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents @@ -331,7 +338,7 @@ fn warning_on_invalid_plugin_item() { name: "example".into(), filename: example_plugin_path, shell: None, - data: PluginRegistryItemData::Valid { commands: vec![] }, + data: valid_plugin_item_data(), }); contents.upsert_plugin(PluginRegistryItem { From 10e84038afe55ba63c9b3187e6d3a1749fa2cc65 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 21 Jun 2024 22:07:57 +0300 Subject: [PATCH 15/38] nu-explore: Add vertical lines && fix index/transpose issue (#13147) Somehow I believe that split lines were implemented originally; (I haven't got to find it though; from a quick look) I mean a long time ago before a lot a changes were made. Probably adding horizontal lines would make also some sense. ref #13116 close #13140 Take care ________________ If `explore` is used, frequently, or planned to be so. I guess it would be a good one to create a test suite for it; to not break things occasionally :sweat_smile: I did approached it one time back then using `expectrl` (literally `expect`), but there was some issues. Maybe smth. did change. Or some `clean` mode could be introduced for it, to being able to be used by outer programs; to control `nu`. Just thoughts here. --- crates/nu-explore/src/views/record/mod.rs | 76 +++++++++------ .../src/views/record/table_widget.rs | 96 ++++++++++++++----- 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index 9e67e08120..87b0952a68 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -131,7 +131,7 @@ impl RecordView { Orientation::Left => (column, row), }; - if row >= layer.count_rows() || column >= layer.count_columns() { + if row >= layer.record_values.len() || column >= layer.column_names.len() { // actually must never happen; unless cursor works incorrectly // if being sure about cursor it can be deleted; return Value::nothing(Span::unknown()); @@ -610,7 +610,7 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 { /// scroll to the end of the data fn tail_data(state: &mut RecordView, page_size: usize) { let layer = state.get_layer_last_mut(); - let count_rows = layer.count_rows(); + let count_rows = layer.record_values.len(); if count_rows > page_size { layer .cursor @@ -722,43 +722,66 @@ fn get_percentage(value: usize, max: usize) -> usize { } fn transpose_table(layer: &mut RecordLayer) { + if layer.was_transposed { + transpose_from(layer); + } else { + transpose_to(layer); + } + + layer.was_transposed = !layer.was_transposed; +} + +fn transpose_from(layer: &mut RecordLayer) { let count_rows = layer.record_values.len(); let count_columns = layer.column_names.len(); - if layer.was_transposed { - let headers = pop_first_column(&mut layer.record_values); - let headers = headers - .into_iter() - .map(|value| match value { - Value::String { val, .. } => val, - _ => unreachable!("must never happen"), - }) - .collect(); + if let Some(data) = &mut layer.record_text { + pop_first_column(data); + *data = _transpose_table(data, count_rows, count_columns - 1); + } - let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1); + let headers = pop_first_column(&mut layer.record_values); + let headers = headers + .into_iter() + .map(|value| match value { + Value::String { val, .. } => val, + _ => unreachable!("must never happen"), + }) + .collect(); - layer.record_values = data; - layer.column_names = headers; + let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1); - return; + layer.record_values = data; + layer.column_names = headers; +} + +fn transpose_to(layer: &mut RecordLayer) { + let count_rows = layer.record_values.len(); + let count_columns = layer.column_names.len(); + + if let Some(data) = &mut layer.record_text { + *data = _transpose_table(data, count_rows, count_columns); + for (column, column_name) in layer.column_names.iter().enumerate() { + let value = (column_name.to_owned(), Default::default()); + data[column].insert(0, value); + } } let mut data = _transpose_table(&layer.record_values, count_rows, count_columns); - for (column, column_name) in layer.column_names.iter().enumerate() { let value = Value::string(column_name, NuSpan::unknown()); - data[column].insert(0, value); } layer.record_values = data; layer.column_names = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect(); - - layer.was_transposed = !layer.was_transposed; } -fn pop_first_column(values: &mut [Vec]) -> Vec { - let mut data = vec![Value::default(); values.len()]; +fn pop_first_column(values: &mut [Vec]) -> Vec +where + T: Default + Clone, +{ + let mut data = vec![T::default(); values.len()]; for (row, values) in values.iter_mut().enumerate() { data[row] = values.remove(0); } @@ -766,12 +789,11 @@ fn pop_first_column(values: &mut [Vec]) -> Vec { data } -fn _transpose_table( - values: &[Vec], - count_rows: usize, - count_columns: usize, -) -> Vec> { - let mut data = vec![vec![Value::default(); count_rows]; count_columns]; +fn _transpose_table(values: &[Vec], count_rows: usize, count_columns: usize) -> Vec> +where + T: Clone + Default, +{ + let mut data = vec![vec![T::default(); count_rows]; count_columns]; for (row, values) in values.iter().enumerate() { for (column, value) in values.iter().enumerate() { data[column][row].clone_from(value); diff --git a/crates/nu-explore/src/views/record/table_widget.rs b/crates/nu-explore/src/views/record/table_widget.rs index 7149a7ce60..ae39d46858 100644 --- a/crates/nu-explore/src/views/record/table_widget.rs +++ b/crates/nu-explore/src/views/record/table_widget.rs @@ -88,6 +88,7 @@ impl StatefulWidget for TableWidget<'_> { // todo: refactoring these to methods as they have quite a bit in common. impl<'a> TableWidget<'a> { + // header at the top; header is always 1 line fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) { let padding_l = self.config.column_padding_left as u16; let padding_r = self.config.column_padding_right as u16; @@ -130,25 +131,16 @@ impl<'a> TableWidget<'a> { } if show_index { - let area = Rect::new(width, data_y, area.width, data_height); width += render_index( buf, - area, + Rect::new(width, data_y, area.width, data_height), self.style_computer, self.index_row, padding_l, padding_r, ); - width += render_vertical_line_with_split( - buf, - width, - data_y, - data_height, - show_head, - false, - separator_s, - ); + width += render_split_line(buf, width, area.y, area.height, show_head, separator_s); } // if there is more data than we can show, add an ellipsis to the column headers to hint at that @@ -162,6 +154,11 @@ impl<'a> TableWidget<'a> { } for col in self.index_column..self.columns.len() { + let need_split_line = state.count_columns > 0 && width < area.width; + if need_split_line { + width += render_split_line(buf, width, area.y, area.height, show_head, separator_s); + } + let mut column = create_column(data, col); let column_width = calculate_column_width(&column); @@ -200,6 +197,7 @@ impl<'a> TableWidget<'a> { } let head_iter = [(&head, head_style)].into_iter(); + // we don't change width here cause the whole column have the same width; so we add it when we print data let mut w = width; w += render_space(buf, w, head_y, 1, padding_l); w += render_column(buf, w, head_y, use_space, head_iter); @@ -209,10 +207,10 @@ impl<'a> TableWidget<'a> { state.layout.push(&head, x, head_y, use_space, 1); } - let head_rows = column.iter().map(|(t, s)| (t, *s)); + let column_rows = column.iter().map(|(t, s)| (t, *s)); width += render_space(buf, width, data_y, data_height, padding_l); - width += render_column(buf, width, data_y, use_space, head_rows); + width += render_column(buf, width, data_y, use_space, column_rows); width += render_space(buf, width, data_y, data_height, padding_r); for (row, (text, _)) in column.iter().enumerate() { @@ -235,15 +233,7 @@ impl<'a> TableWidget<'a> { } if width < area.width { - width += render_vertical_line_with_split( - buf, - width, - data_y, - data_height, - show_head, - false, - separator_s, - ); + width += render_split_line(buf, width, area.y, area.height, show_head, separator_s); } let rest = area.width.saturating_sub(width); @@ -255,6 +245,7 @@ impl<'a> TableWidget<'a> { } } + // header at the left; header is always 1 line fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) { if area.width == 0 || area.height == 0 { return; @@ -353,6 +344,9 @@ impl<'a> TableWidget<'a> { state.count_rows = columns.len(); state.count_columns = 0; + // note: is there a time where we would have more then 1 column? + // seems like not really; cause it's literally KV table, or am I wrong? + for col in self.index_column..self.data.len() { let mut column = self.data[col][self.index_row..self.index_row + columns.len()].to_vec(); @@ -361,6 +355,13 @@ impl<'a> TableWidget<'a> { break; } + // see KV comment; this block might never got used + let need_split_line = state.count_columns > 0 && left_w < area.width; + if need_split_line { + render_vertical_line(buf, area.x + left_w, area.y, area.height, separator_s); + left_w += 1; + } + let column_width = column_width as u16; let available = area.width - left_w; let is_last = col + 1 == self.data.len(); @@ -555,6 +556,51 @@ fn render_index( width } +fn render_split_line( + buf: &mut Buffer, + x: u16, + y: u16, + height: u16, + has_head: bool, + style: NuStyle, +) -> u16 { + if has_head { + render_vertical_split_line(buf, x, y, height, &[0], &[2], &[], style); + } else { + render_vertical_split_line(buf, x, y, height, &[], &[], &[], style); + } + + 1 +} + +#[allow(clippy::too_many_arguments)] +fn render_vertical_split_line( + buf: &mut Buffer, + x: u16, + y: u16, + height: u16, + top_slit: &[u16], + inner_slit: &[u16], + bottom_slit: &[u16], + style: NuStyle, +) -> u16 { + render_vertical_line(buf, x, y, height, style); + + for &y in top_slit { + render_top_connector(buf, x, y, style); + } + + for &y in inner_slit { + render_inner_connector(buf, x, y, style); + } + + for &y in bottom_slit { + render_bottom_connector(buf, x, y, style); + } + + 1 +} + fn render_vertical_line_with_split( buf: &mut Buffer, x: u16, @@ -668,6 +714,12 @@ fn render_bottom_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) { buf.set_span(x, y, &span, 1); } +fn render_inner_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) { + let style = nu_style_to_tui(style); + let span = Span::styled("┼", style); + buf.set_span(x, y, &span, 1); +} + fn calculate_column_width(column: &[NuText]) -> usize { column .iter() From db86dd9f267e46a7a8aeff75ab87b3d0832d1e00 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Sat, 22 Jun 2024 05:23:42 -0700 Subject: [PATCH 16/38] Polars default infer (#13193) Addresses performance issues that @maxim-uvarov found with CSV and JSON lines. This ensures that the schema inference follows the polars defaults of 100 lines. Recent changes caused the default values to be override and caused the entire file to be scanned when inferring the schema. --- .../src/dataframe/eager/open.rs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/nu_plugin_polars/src/dataframe/eager/open.rs b/crates/nu_plugin_polars/src/dataframe/eager/open.rs index 8842e3c7fb..f7ee467877 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/open.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/open.rs @@ -32,6 +32,8 @@ use polars_io::{ avro::AvroReader, csv::read::CsvReadOptions, prelude::ParallelStrategy, HiveOptions, }; +const DEFAULT_INFER_SCHEMA: usize = 100; + #[derive(Clone)] pub struct OpenDataFrame; @@ -374,7 +376,9 @@ fn from_jsonl( file_path: &Path, file_span: Span, ) -> Result { - let infer_schema: Option = call.get_flag("infer-schema")?; + let infer_schema: usize = call + .get_flag("infer-schema")? + .unwrap_or(DEFAULT_INFER_SCHEMA); let maybe_schema = call .get_flag("schema")? .map(|schema| NuSchema::try_from(&schema)) @@ -384,7 +388,7 @@ fn from_jsonl( let start_time = std::time::Instant::now(); let df = LazyJsonLineReader::new(file_path) - .with_infer_schema_length(infer_schema) + .with_infer_schema_length(Some(infer_schema)) .with_schema(maybe_schema.map(|s| s.into())) .finish() .map_err(|e| ShellError::GenericError { @@ -417,7 +421,7 @@ fn from_jsonl( let buf_reader = BufReader::new(file); let reader = JsonReader::new(buf_reader) .with_json_format(JsonFormat::JsonLines) - .infer_schema_len(infer_schema); + .infer_schema_len(Some(infer_schema)); let reader = match maybe_schema { Some(schema) => reader.with_schema(schema.into()), @@ -459,7 +463,9 @@ fn from_csv( ) -> Result { let delimiter: Option> = call.get_flag("delimiter")?; let no_header: bool = call.has_flag("no-header")?; - let infer_schema: Option = call.get_flag("infer-schema")?; + let infer_schema: usize = call + .get_flag("infer-schema")? + .unwrap_or(DEFAULT_INFER_SCHEMA); let skip_rows: Option = call.get_flag("skip-rows")?; let columns: Option> = call.get_flag("columns")?; @@ -499,10 +505,7 @@ fn from_csv( None => csv_reader, }; - let csv_reader = match infer_schema { - None => csv_reader, - Some(r) => csv_reader.with_infer_schema_length(Some(r)), - }; + let csv_reader = csv_reader.with_infer_schema_length(Some(infer_schema)); let csv_reader = match skip_rows { None => csv_reader, @@ -535,7 +538,7 @@ fn from_csv( let start_time = std::time::Instant::now(); let df = CsvReadOptions::default() .with_has_header(!no_header) - .with_infer_schema_length(infer_schema) + .with_infer_schema_length(Some(infer_schema)) .with_skip_rows(skip_rows.unwrap_or_default()) .with_schema(maybe_schema.map(|s| s.into())) .with_columns(columns.map(Arc::new)) From dcb6ab63703caf8917793fcbce2a39967ab819d5 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sat, 22 Jun 2024 08:37:34 -0400 Subject: [PATCH 17/38] Fixed `generate` command signature (#13200) # Description Removes `list` as an input type for the `generate` command. This command does not accept pipeline input (and cannot, logically). This can be seen by the use of `_input` in the command's `run()`. Also, due to #13199, in order to pass `toolkit check pr`, one of the examples was changed to remove the `result`. This is probably a better demonstration of the ability of the command to infinitely generate a list anyway, and an infinite list can't be represented in a `result`. # User-Facing Changes Should only be a change to the help. The input type was never valid and couldn't have been used. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` --- crates/nu-command/src/generators/generate.rs | 29 ++++---------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 3549667ff0..aeb375aab0 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -12,13 +12,7 @@ impl Command for Generate { fn signature(&self) -> Signature { Signature::build("generate") - .input_output_types(vec![ - (Type::Nothing, Type::List(Box::new(Type::Any))), - ( - Type::List(Box::new(Type::Any)), - Type::List(Box::new(Type::Any)), - ), - ]) + .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))]) .required("initial", SyntaxShape::Any, "Initial value.") .required( "closure", @@ -63,23 +57,10 @@ used as the next argument to the closure, otherwise generation stops. )), }, Example { - example: "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10", - description: "Generate a stream of fibonacci numbers", - result: Some(Value::list( - vec![ - Value::test_int(0), - Value::test_int(1), - Value::test_int(1), - Value::test_int(2), - Value::test_int(3), - Value::test_int(5), - Value::test_int(8), - Value::test_int(13), - Value::test_int(21), - Value::test_int(34), - ], - Span::test_data(), - )), + example: + "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }", + description: "Generate a continuous stream of Fibonacci numbers", + result: None, }, ] } From b6bdadbc6fdbab4d86826f72fe97bf2fd0ad00a6 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sat, 22 Jun 2024 08:41:29 -0400 Subject: [PATCH 18/38] Suppress column index for default `cal` output (#13188) # Description * As discussed in the comments in #11954, this suppresses the index column on `cal` output. It does that by running `table -i false` on the results by default. * Added new `--as-table/-t` flag to revert to the old behavior and output the calendar as structured data * Updated existing tests to use `--as-table` * Added new tests against the string output * Updated `length` test which also used `cal` * Added new example for `--as-table`, with result # User-Facing Changes ## Breaking change The *default* `cal` output has changed from a `list` to a `string`. To obtain structured data from `cal`, use the new `--as-table/-t` flag. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting --- crates/nu-command/src/generators/cal.rs | 38 +++++++++++++++-- crates/nu-command/tests/commands/cal.rs | 47 +++++++++++++++++++--- crates/nu-command/tests/commands/length.rs | 2 +- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/crates/nu-command/src/generators/cal.rs b/crates/nu-command/src/generators/cal.rs index e1ecc771de..a257f3ab7c 100644 --- a/crates/nu-command/src/generators/cal.rs +++ b/crates/nu-command/src/generators/cal.rs @@ -1,6 +1,7 @@ use chrono::{Datelike, Local, NaiveDate}; use nu_color_config::StyleComputer; use nu_engine::command_prelude::*; +use nu_protocol::ast::{Expr, Expression}; use std::collections::VecDeque; @@ -14,6 +15,7 @@ struct Arguments { month_names: bool, full_year: Option>, week_start: Option>, + as_table: bool, } impl Command for Cal { @@ -26,6 +28,7 @@ impl Command for Cal { .switch("year", "Display the year column", Some('y')) .switch("quarter", "Display the quarter column", Some('q')) .switch("month", "Display the month column", Some('m')) + .switch("as-table", "output as a table", Some('t')) .named( "full-year", SyntaxShape::Int, @@ -43,7 +46,10 @@ impl Command for Cal { "Display the month names instead of integers", None, ) - .input_output_types(vec![(Type::Nothing, Type::table())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::Nothing, Type::String), + ]) .allow_variants_without_examples(true) // TODO: supply exhaustive examples .category(Category::Generators) } @@ -75,10 +81,15 @@ impl Command for Cal { result: None, }, Example { - description: "This month's calendar with the week starting on monday", + description: "This month's calendar with the week starting on Monday", example: "cal --week-start mo", result: None, }, + Example { + description: "How many 'Friday the Thirteenths' occurred in 2015?", + example: "cal --as-table --full-year 2015 | where fr == 13 | length", + result: None, + }, ] } } @@ -101,6 +112,7 @@ pub fn cal( quarter: call.has_flag(engine_state, stack, "quarter")?, full_year: call.get_flag(engine_state, stack, "full-year")?, week_start: call.get_flag(engine_state, stack, "week-start")?, + as_table: call.has_flag(engine_state, stack, "as-table")?, }; let style_computer = &StyleComputer::from_config(engine_state, stack); @@ -131,7 +143,27 @@ pub fn cal( style_computer, )?; - Ok(Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data()) + let mut table_no_index = Call::new(Span::unknown()); + table_no_index.add_named(( + Spanned { + item: "index".to_string(), + span: Span::unknown(), + }, + None, + Some(Expression::new_unknown( + Expr::Bool(false), + Span::unknown(), + Type::Bool, + )), + )); + + let cal_table_output = + Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data(); + if !arguments.as_table { + crate::Table.run(engine_state, stack, &table_no_index, cal_table_output) + } else { + Ok(cal_table_output) + } } fn get_invalid_year_shell_error(head: Span) -> ShellError { diff --git a/crates/nu-command/tests/commands/cal.rs b/crates/nu-command/tests/commands/cal.rs index 651ab8d3cd..65d6306845 100644 --- a/crates/nu-command/tests/commands/cal.rs +++ b/crates/nu-command/tests/commands/cal.rs @@ -1,8 +1,9 @@ use nu_test_support::{nu, pipeline}; +// Tests against table/structured data #[test] fn cal_full_year() { - let actual = nu!("cal -y --full-year 2010 | first | to json -r"); + let actual = nu!("cal -t -y --full-year 2010 | first | to json -r"); let first_week_2010_json = r#"{"year":2010,"su":null,"mo":null,"tu":null,"we":null,"th":null,"fr":1,"sa":2}"#; @@ -14,7 +15,7 @@ fn cal_full_year() { fn cal_february_2020_leap_year() { let actual = nu!(pipeline( r#" - cal -ym --full-year 2020 --month-names | where month == "february" | to json -r + cal --as-table -ym --full-year 2020 --month-names | where month == "february" | to json -r "# )); @@ -27,7 +28,7 @@ fn cal_february_2020_leap_year() { fn cal_fr_the_thirteenths_in_2015() { let actual = nu!(pipeline( r#" - cal --full-year 2015 | default 0 fr | where fr == 13 | length + cal --as-table --full-year 2015 | default 0 fr | where fr == 13 | length "# )); @@ -38,7 +39,7 @@ fn cal_fr_the_thirteenths_in_2015() { fn cal_rows_in_2020() { let actual = nu!(pipeline( r#" - cal --full-year 2020 | length + cal --as-table --full-year 2020 | length "# )); @@ -49,7 +50,7 @@ fn cal_rows_in_2020() { fn cal_week_day_start_mo() { let actual = nu!(pipeline( r#" - cal --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r + cal --as-table --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r "# )); @@ -62,9 +63,43 @@ fn cal_week_day_start_mo() { fn cal_sees_pipeline_year() { let actual = nu!(pipeline( r#" - cal --full-year 1020 | get mo | first 4 | to json -r + cal --as-table --full-year 1020 | get mo | first 4 | to json -r "# )); assert_eq!(actual.out, "[null,3,10,17]"); } + +// Tests against default string output +#[test] +fn cal_is_string() { + let actual = nu!(pipeline( + r#" + cal | describe + "# + )); + + assert_eq!(actual.out, "string (stream)"); +} + +#[test] +fn cal_year_num_lines() { + let actual = nu!(pipeline( + r#" + cal --full-year 2024 | lines | length + "# + )); + + assert_eq!(actual.out, "68"); +} + +#[test] +fn cal_week_start_string() { + let actual = nu!(pipeline( + r#" + cal --week-start fr | lines | get 1 | split row '│' | get 2 | ansi strip | str trim + "# + )); + + assert_eq!(actual.out, "sa"); +} diff --git a/crates/nu-command/tests/commands/length.rs b/crates/nu-command/tests/commands/length.rs index 63318e65db..b67ac452e8 100644 --- a/crates/nu-command/tests/commands/length.rs +++ b/crates/nu-command/tests/commands/length.rs @@ -2,7 +2,7 @@ use nu_test_support::nu; #[test] fn length_columns_in_cal_table() { - let actual = nu!("cal | columns | length"); + let actual = nu!("cal --as-table | columns | length"); assert_eq!(actual.out, "7"); } From 9b7f899410e0f5bbeddd75d1c431c927c3e610af Mon Sep 17 00:00:00 2001 From: Piepmatz Date: Sat, 22 Jun 2024 22:31:09 +0200 Subject: [PATCH 19/38] Allow missing fields in derived `FromValue::from_value` calls (#13206) # Description In #13031 I added the derive macros for `FromValue` and `IntoValue`. In that implementation, in particular for structs with named fields, it was not possible to omit fields while loading them from a value, when the field is an `Option`. This PR adds extra handling for this behavior, so if a field is an `Option` and that field is missing in the `Value`, then the field becomes `None`. This behavior is also tested in `nu_protocol::value::test_derive::missing_options`. # User-Facing Changes When using structs for options or similar, users can now just emit fields in the record and the derive `from_value` method will be able to understand this, if the struct has an `Option` type for that field. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting A showcase for this feature would be great, I tried to use the current derive macro in a plugin of mine for a config but without this addition, they are annoying to use. So, when this is done, I would add an example for such plugin configs that may be loaded via `FromValue`. --- crates/nu-derive-value/src/from.rs | 66 +++++++++++++++------ crates/nu-protocol/src/value/test_derive.rs | 56 +++++++++++++++++ 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/crates/nu-derive-value/src/from.rs b/crates/nu-derive-value/src/from.rs index 033026c149..783a22920e 100644 --- a/crates/nu-derive-value/src/from.rs +++ b/crates/nu-derive-value/src/from.rs @@ -3,6 +3,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::{ spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, + Type, }; use crate::attributes::{self, ContainerAttributes}; @@ -116,15 +117,11 @@ fn derive_struct_from_value( /// src_span: span /// })?, /// )?, -/// favorite_toy: as nu_protocol::FromValue>::from_value( -/// record -/// .remove("favorite_toy") -/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { -/// col_name: std::string::ToString::to_string("favorite_toy"), -/// span: std::option::Option::None, -/// src_span: span -/// })?, -/// )?, +/// favorite_toy: record +/// .remove("favorite_toy") +/// .map(|v| <#ty as nu_protocol::FromValue>::from_value(v)) +/// .transpose()? +/// .flatten(), /// }) /// } /// } @@ -480,20 +477,29 @@ fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenSt match fields { Fields::Named(fields) => { let fields = fields.named.iter().map(|field| { - // TODO: handle missing fields for Options as None let ident = field.ident.as_ref().expect("named has idents"); let ident_s = ident.to_string(); let ty = &field.ty; - quote! { - #ident: <#ty as nu_protocol::FromValue>::from_value( - record + match type_is_option(ty) { + true => quote! { + #ident: record .remove(#ident_s) - .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { - col_name: std::string::ToString::to_string(#ident_s), - span: std::option::Option::None, - src_span: span - })?, - )? + .map(|v| <#ty as nu_protocol::FromValue>::from_value(v)) + .transpose()? + .flatten() + }, + + false => quote! { + #ident: <#ty as nu_protocol::FromValue>::from_value( + record + .remove(#ident_s) + .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { + col_name: std::string::ToString::to_string(#ident_s), + span: std::option::Option::None, + src_span: span + })?, + )? + }, } }); quote! { @@ -537,3 +543,25 @@ fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenSt }, } } + +const FULLY_QUALIFIED_OPTION: &str = "std::option::Option"; +const PARTIALLY_QUALIFIED_OPTION: &str = "option::Option"; +const PRELUDE_OPTION: &str = "Option"; + +/// Check if the field type is an `Option`. +/// +/// This function checks if a given type is an `Option`. +/// We assume that an `Option` is [`std::option::Option`] because we can't see the whole code and +/// can't ask the compiler itself. +/// If the `Option` type isn't `std::option::Option`, the user will get a compile error due to a +/// type mismatch. +/// It's very unusual for people to override `Option`, so this should rarely be an issue. +/// +/// When [rust#63084](https://github.com/rust-lang/rust/issues/63084) is resolved, we can use +/// [`std::any::type_name`] for a static assertion check to get a more direct error messages. +fn type_is_option(ty: &Type) -> bool { + let s = ty.to_token_stream().to_string(); + s.starts_with(PRELUDE_OPTION) + || s.starts_with(PARTIALLY_QUALIFIED_OPTION) + || s.starts_with(FULLY_QUALIFIED_OPTION) +} diff --git a/crates/nu-protocol/src/value/test_derive.rs b/crates/nu-protocol/src/value/test_derive.rs index 1865a05418..56bccabd2a 100644 --- a/crates/nu-protocol/src/value/test_derive.rs +++ b/crates/nu-protocol/src/value/test_derive.rs @@ -171,6 +171,62 @@ fn named_fields_struct_incorrect_type() { assert!(res.is_err()); } +#[derive(IntoValue, FromValue, Debug, PartialEq, Default)] +struct ALotOfOptions { + required: bool, + float: Option, + int: Option, + value: Option, + nested: Option, +} + +#[test] +fn missing_options() { + let value = Value::test_record(Record::new()); + let res: Result = ALotOfOptions::from_value(value); + assert!(res.is_err()); + + let value = Value::test_record(record! {"required" => Value::test_bool(true)}); + let expected = ALotOfOptions { + required: true, + ..Default::default() + }; + let actual = ALotOfOptions::from_value(value).unwrap(); + assert_eq!(expected, actual); + + let value = Value::test_record(record! { + "required" => Value::test_bool(true), + "float" => Value::test_float(std::f64::consts::PI), + }); + let expected = ALotOfOptions { + required: true, + float: Some(std::f64::consts::PI), + ..Default::default() + }; + let actual = ALotOfOptions::from_value(value).unwrap(); + assert_eq!(expected, actual); + + let value = Value::test_record(record! { + "required" => Value::test_bool(true), + "int" => Value::test_int(12), + "nested" => Value::test_record(record! { + "u32" => Value::test_int(34), + }), + }); + let expected = ALotOfOptions { + required: true, + int: Some(12), + nested: Some(Nestee { + u32: 34, + some: None, + none: None, + }), + ..Default::default() + }; + let actual = ALotOfOptions::from_value(value).unwrap(); + assert_eq!(expected, actual); +} + #[derive(IntoValue, FromValue, Debug, PartialEq)] struct UnnamedFieldsStruct(u32, String, T) where From 4509944988fe9607bc5256702afb9a7982c76610 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 24 Jun 2024 01:43:05 +0200 Subject: [PATCH 20/38] Fix usage parsing for commands defined in CRLF (windows) files (#13212) Fixes nushell/nushell#13207 # Description This fixes the parsing of command usage when that command comes from a file with CRLF line endings. See nushell/nushell#13207 for more details. # User-Facing Changes Users on Windows will get correct autocompletion for `std` commands. --- crates/nu-protocol/src/engine/usage.rs | 4 +++- tests/repl/test_parser.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/engine/usage.rs b/crates/nu-protocol/src/engine/usage.rs index a9cf2c25f1..76f50d2424 100644 --- a/crates/nu-protocol/src/engine/usage.rs +++ b/crates/nu-protocol/src/engine/usage.rs @@ -81,7 +81,9 @@ pub(super) fn build_usage(comment_lines: &[&[u8]]) -> (String, String) { usage.push_str(&comment_line); } - if let Some((brief_usage, extra_usage)) = usage.split_once("\n\n") { + if let Some((brief_usage, extra_usage)) = usage.split_once("\r\n\r\n") { + (brief_usage.to_string(), extra_usage.to_string()) + } else if let Some((brief_usage, extra_usage)) = usage.split_once("\n\n") { (brief_usage.to_string(), extra_usage.to_string()) } else { (usage, String::default()) diff --git a/tests/repl/test_parser.rs b/tests/repl/test_parser.rs index 9d743b175d..0efb1d087a 100644 --- a/tests/repl/test_parser.rs +++ b/tests/repl/test_parser.rs @@ -261,6 +261,22 @@ fn commands_have_usage() -> TestResult { ) } +#[test] +fn commands_from_crlf_source_have_short_usage() -> TestResult { + run_test_contains( + "# This is a test\r\n#\r\n# To see if I have cool usage\r\ndef foo [] {}\r\nscope commands | where name == foo | get usage.0", + "This is a test", + ) +} + +#[test] +fn commands_from_crlf_source_have_extra_usage() -> TestResult { + run_test_contains( + "# This is a test\r\n#\r\n# To see if I have cool usage\r\ndef foo [] {}\r\nscope commands | where name == foo | get extra_usage.0", + "To see if I have cool usage", + ) +} + #[test] fn equals_separates_long_flag() -> TestResult { run_test( From 43e349a274d955185f7a199ddf1db0e4acd6c498 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Mon, 24 Jun 2024 16:39:01 -0700 Subject: [PATCH 21/38] Mitigate the poor interaction between ndots expansion and non-path strings (#13218) # Description @hustcer reported that slashes were disappearing from external args since #13089: ``` $> ossutil ls oss://abc/b/c Error: invalid cloud url: "oss:/abc/b/c", please make sure the url starts with: "oss://" $> ossutil ls 'oss://abc/b/c' Error: oss: service returned error: StatusCode=403, ErrorCode=UserDisable, ErrorMessage="UserDisable", RequestId=66791EDEFE87B73537120838, Ec=0003-00000801, Bucket=abc, Object= ``` I narrowed this down to the ndots handling, since that does path parsing and path reconstruction in every case. I decided to change that so that it only activates if the string contains at least `...`, since that would be the minimum trigger for ndots, and also to not activate it if the string contains `://`, since it's probably undesirable for a URL. Kind of a hack, but I'm not really sure how else we decide whether someone wants ndots or not. # User-Facing Changes - bare strings not containing ndots are not modified - bare strings containing `://` are not modified # Tests + Formatting Added tests to prevent regression. --- crates/nu-command/src/system/run_external.rs | 23 +++++++++++-- .../nu-command/tests/commands/run_external.rs | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 104729a786..f82c23a0e0 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -64,7 +64,9 @@ impl Command for External { let expanded_name = match &name { // Expand tilde and ndots on the name if it's a bare string / glob (#13000) - Value::Glob { no_expand, .. } if !*no_expand => expand_ndots(expand_tilde(&*name_str)), + Value::Glob { no_expand, .. } if !*no_expand => { + expand_ndots_safe(expand_tilde(&*name_str)) + } _ => Path::new(&*name_str).to_owned(), }; @@ -294,7 +296,7 @@ fn expand_glob( // For an argument that doesn't include the GLOB_CHARS, just do the `expand_tilde` // and `expand_ndots` expansion if !arg.contains(GLOB_CHARS) { - let path = expand_ndots(expand_tilde(arg)); + let path = expand_ndots_safe(expand_tilde(arg)); return Ok(vec![path.into()]); } @@ -582,6 +584,21 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, ShellE } } +/// Expand ndots, but only if it looks like it probably contains them, because there is some lossy +/// path normalization that happens. +fn expand_ndots_safe(path: impl AsRef) -> PathBuf { + let string = path.as_ref().to_string_lossy(); + + // Use ndots if it contains at least `...`, since that's the minimum trigger point, and don't + // use it if it contains ://, because that looks like a URL scheme and the path normalization + // will mess with that. + if string.contains("...") && !string.contains("://") { + expand_ndots(path) + } else { + path.as_ref().to_owned() + } +} + #[cfg(test)] mod test { use super::*; @@ -610,7 +627,7 @@ mod test { assert_eq!(actual, expected); let actual = expand_glob("./a.txt", cwd, Span::unknown(), &None).unwrap(); - let expected: Vec = vec![Path::new(".").join("a.txt").into()]; + let expected = &["./a.txt"]; assert_eq!(actual, expected); let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index daa5abb25b..154c31b71c 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -229,6 +229,38 @@ fn external_command_escape_args() { }) } +#[test] +fn external_command_ndots_args() { + let actual = nu!(r#" + nu --testbin cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar + "#); + + assert_eq!( + actual.out, + if cfg!(windows) { + // Windows is a bit weird right now, where if ndots has to fix something it's going to + // change everything to backslashes too. Would be good to fix that + r"foo/. foo/.. foo\..\.. foo/./bar foo/../bar foo\..\..\bar ./bar ../bar ..\..\bar" + } else { + r"foo/. foo/.. foo/../.. foo/./bar foo/../bar foo/../../bar ./bar ../bar ../../bar" + } + ); +} + +#[test] +fn external_command_url_args() { + // If ndots is not handled correctly, we can lose the double forward slashes that are needed + // here + let actual = nu!(r#" + nu --testbin cococo http://example.com http://example.com/.../foo //foo + "#); + + assert_eq!( + actual.out, + "http://example.com http://example.com/.../foo //foo" + ); +} + #[test] #[cfg_attr( not(target_os = "linux"), From f93c6680bd5ae158190556eb1afc924b0290b52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 25 Jun 2024 21:29:47 +0300 Subject: [PATCH 22/38] Bump to 0.95.0 (#13221) # Description # User-Facing Changes # Tests + Formatting # After Submitting --- Cargo.lock | 74 +++++++++---------- Cargo.toml | 42 +++++------ crates/nu-cli/Cargo.toml | 26 +++---- crates/nu-cmd-base/Cargo.toml | 12 +-- crates/nu-cmd-extra/Cargo.toml | 22 +++--- crates/nu-cmd-lang/Cargo.toml | 12 +-- crates/nu-cmd-plugin/Cargo.toml | 12 +-- crates/nu-color-config/Cargo.toml | 10 +-- crates/nu-command/Cargo.toml | 36 ++++----- crates/nu-derive-value/Cargo.toml | 4 +- crates/nu-engine/Cargo.toml | 12 +-- crates/nu-explore/Cargo.toml | 20 ++--- crates/nu-glob/Cargo.toml | 4 +- crates/nu-json/Cargo.toml | 6 +- crates/nu-lsp/Cargo.toml | 16 ++-- crates/nu-parser/Cargo.toml | 12 +-- crates/nu-path/Cargo.toml | 4 +- crates/nu-plugin-core/Cargo.toml | 8 +- crates/nu-plugin-engine/Cargo.toml | 14 ++-- crates/nu-plugin-protocol/Cargo.toml | 8 +- crates/nu-plugin-test-support/Cargo.toml | 20 ++--- crates/nu-plugin/Cargo.toml | 12 +-- crates/nu-pretty-hex/Cargo.toml | 4 +- crates/nu-protocol/Cargo.toml | 14 ++-- crates/nu-std/Cargo.toml | 10 +-- crates/nu-system/Cargo.toml | 4 +- crates/nu-table/Cargo.toml | 12 +-- crates/nu-term-grid/Cargo.toml | 6 +- crates/nu-test-support/Cargo.toml | 10 +-- crates/nu-utils/Cargo.toml | 4 +- .../src/sample_config/default_config.nu | 4 +- .../nu-utils/src/sample_config/default_env.nu | 4 +- crates/nu_plugin_custom_values/Cargo.toml | 6 +- crates/nu_plugin_example/Cargo.toml | 10 +-- crates/nu_plugin_formats/Cargo.toml | 8 +- crates/nu_plugin_gstat/Cargo.toml | 8 +- crates/nu_plugin_inc/Cargo.toml | 8 +- .../nu_plugin_nu_example.nu | 4 +- crates/nu_plugin_polars/Cargo.toml | 20 ++--- .../nu_plugin_python_example.py | 4 +- crates/nu_plugin_query/Cargo.toml | 8 +- crates/nu_plugin_stress_internals/Cargo.toml | 4 +- crates/nuon/Cargo.toml | 10 +-- 43 files changed, 274 insertions(+), 274 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba1880d9b5..9b8f5273c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2769,7 +2769,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.94.3" +version = "0.95.0" dependencies = [ "assert_cmd", "crossterm", @@ -2822,7 +2822,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.94.3" +version = "0.95.0" dependencies = [ "chrono", "crossterm", @@ -2857,7 +2857,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.94.3" +version = "0.95.0" dependencies = [ "indexmap", "miette", @@ -2869,7 +2869,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.94.3" +version = "0.95.0" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2894,7 +2894,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.94.3" +version = "0.95.0" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2906,7 +2906,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.94.3" +version = "0.95.0" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2917,7 +2917,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-ansi-term", "nu-engine", @@ -2929,7 +2929,7 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.94.3" +version = "0.95.0" dependencies = [ "alphanumeric-sort", "base64 0.22.1", @@ -3038,7 +3038,7 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.94.3" +version = "0.95.0" dependencies = [ "convert_case", "proc-macro-error", @@ -3049,7 +3049,7 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-glob", "nu-path", @@ -3059,7 +3059,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.94.3" +version = "0.95.0" dependencies = [ "ansi-str", "anyhow", @@ -3084,14 +3084,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.94.3" +version = "0.95.0" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.94.3" +version = "0.95.0" dependencies = [ "linked-hash-map", "num-traits", @@ -3101,7 +3101,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.94.3" +version = "0.95.0" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3122,7 +3122,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.94.3" +version = "0.95.0" dependencies = [ "bytesize", "chrono", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.94.3" +version = "0.95.0" dependencies = [ "dirs-next", "omnipath", @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.94.3" +version = "0.95.0" dependencies = [ "log", "nix", @@ -3162,7 +3162,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.94.3" +version = "0.95.0" dependencies = [ "interprocess", "log", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.94.3" +version = "0.95.0" dependencies = [ "log", "nu-engine", @@ -3191,7 +3191,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.94.3" +version = "0.95.0" dependencies = [ "bincode", "nu-protocol", @@ -3203,7 +3203,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3221,7 +3221,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.94.3" +version = "0.95.0" dependencies = [ "heapless", "nu-ansi-term", @@ -3230,7 +3230,7 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.94.3" +version = "0.95.0" dependencies = [ "brotli", "byte-unit", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.94.3" +version = "0.95.0" dependencies = [ "log", "miette", @@ -3274,7 +3274,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.94.3" +version = "0.95.0" dependencies = [ "chrono", "itertools 0.12.1", @@ -3292,7 +3292,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.94.3" +version = "0.95.0" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3306,7 +3306,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-utils", "unicode-width", @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-glob", "nu-path", @@ -3326,7 +3326,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.94.3" +version = "0.95.0" dependencies = [ "crossterm_winapi", "log", @@ -3352,7 +3352,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3362,7 +3362,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.94.3" +version = "0.95.0" dependencies = [ "eml-parser", "ical", @@ -3375,7 +3375,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.94.3" +version = "0.95.0" dependencies = [ "git2", "nu-plugin", @@ -3384,7 +3384,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.94.3" +version = "0.95.0" dependencies = [ "nu-plugin", "nu-protocol", @@ -3393,7 +3393,7 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.94.3" +version = "0.95.0" dependencies = [ "chrono", "chrono-tz 0.9.0", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.94.3" +version = "0.95.0" dependencies = [ "gjson", "nu-plugin", @@ -3436,7 +3436,7 @@ dependencies = [ [[package]] name = "nu_plugin_stress_internals" -version = "0.94.3" +version = "0.95.0" dependencies = [ "interprocess", "serde", @@ -3562,7 +3562,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.94.3" +version = "0.95.0" dependencies = [ "chrono", "fancy-regex", diff --git a/Cargo.toml b/Cargo.toml index 6fce7d3c01..696c012b80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -180,22 +180,22 @@ windows = "0.54" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.94.3" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.3" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.3" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.3", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.3" } -nu-command = { path = "./crates/nu-command", version = "0.94.3" } -nu-engine = { path = "./crates/nu-engine", version = "0.94.3" } -nu-explore = { path = "./crates/nu-explore", version = "0.94.3" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.3" } -nu-parser = { path = "./crates/nu-parser", version = "0.94.3" } -nu-path = { path = "./crates/nu-path", version = "0.94.3" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.3" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.94.3" } -nu-std = { path = "./crates/nu-std", version = "0.94.3" } -nu-system = { path = "./crates/nu-system", version = "0.94.3" } -nu-utils = { path = "./crates/nu-utils", version = "0.94.3" } +nu-cli = { path = "./crates/nu-cli", version = "0.95.0" } +nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.0" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.0" } +nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.0", optional = true } +nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.0" } +nu-command = { path = "./crates/nu-command", version = "0.95.0" } +nu-engine = { path = "./crates/nu-engine", version = "0.95.0" } +nu-explore = { path = "./crates/nu-explore", version = "0.95.0" } +nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.0" } +nu-parser = { path = "./crates/nu-parser", version = "0.95.0" } +nu-path = { path = "./crates/nu-path", version = "0.95.0" } +nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.0" } +nu-protocol = { path = "./crates/nu-protocol", version = "0.95.0" } +nu-std = { path = "./crates/nu-std", version = "0.95.0" } +nu-system = { path = "./crates/nu-system", version = "0.95.0" } +nu-utils = { path = "./crates/nu-utils", version = "0.95.0" } reedline = { workspace = true, features = ["bashisms", "sqlite"] } @@ -225,9 +225,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.94.3" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.3" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.3" } +nu-test-support = { path = "./crates/nu-test-support", version = "0.95.0" } +nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.0" } +nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.0" } assert_cmd = "2.0" dirs-next = { workspace = true } tango-bench = "0.5" @@ -310,4 +310,4 @@ bench = false # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- parse` [[bench]] name = "benchmarks" -harness = false +harness = false \ No newline at end of file diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index c2ca2c9704..8e7c7b5535 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli" edition = "2021" license = "MIT" name = "nu-cli" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } -nu-command = { path = "../nu-command", version = "0.94.3" } -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-command = { path = "../nu-command", version = "0.95.0" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } rstest = { workspace = true, default-features = false } tempfile = { workspace = true } [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } -nu-color-config = { path = "../nu-color-config", version = "0.94.3" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0", optional = true } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-color-config = { path = "../nu-color-config", version = "0.95.0" } nu-ansi-term = { workspace = true } reedline = { workspace = true, features = ["bashisms", "sqlite"] } @@ -46,4 +46,4 @@ which = { workspace = true } [features] plugin = ["nu-plugin-engine"] -system-clipboard = ["reedline/system_clipboard"] +system-clipboard = ["reedline/system_clipboard"] \ No newline at end of file diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index fb6a977ab9..773ee05414 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,17 +5,17 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } indexmap = { workspace = true } miette = { workspace = true } -[dev-dependencies] +[dev-dependencies] \ No newline at end of file diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index 038c9421e8..30c94420a3 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -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.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,13 +13,13 @@ version = "0.94.3" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-json = { version = "0.94.3", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-pretty-hex = { version = "0.94.3", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-json = { version = "0.95.0", path = "../nu-json" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-pretty-hex = { version = "0.95.0", path = "../nu-pretty-hex" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } # Potential dependencies for extras heck = { workspace = true } @@ -33,6 +33,6 @@ v_htmlescape = { workspace = true } itertools = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } -nu-command = { path = "../nu-command", version = "0.94.3" } -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-command = { path = "../nu-command", version = "0.95.0" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index dcd940ba58..6db0d4c096 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } itertools = { workspace = true } shadow-rs = { version = "0.28", default-features = false } @@ -28,4 +28,4 @@ mimalloc = [] trash-support = [] sqlite = [] static-link-openssl = [] -system-clipboard = [] +system-clipboard = [] \ No newline at end of file diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index d274f46a6a..f6eccb753e 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,16 +5,16 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0" } itertools = { workspace = true } -[dev-dependencies] +[dev-dependencies] \ No newline at end of file diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 95b06c1f77..34f15cd74f 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-json = { path = "../nu-json", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-json = { path = "../nu-json", version = "0.95.0" } nu-ansi-term = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index f16d62d836..355232b938 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,21 +13,21 @@ version = "0.94.3" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } -nu-color-config = { path = "../nu-color-config", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-glob = { path = "../nu-glob", version = "0.94.3" } -nu-json = { path = "../nu-json", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-system = { path = "../nu-system", version = "0.94.3" } -nu-table = { path = "../nu-table", version = "0.94.3" } -nu-term-grid = { path = "../nu-term-grid", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } +nu-color-config = { path = "../nu-color-config", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-glob = { path = "../nu-glob", version = "0.95.0" } +nu-json = { path = "../nu-json", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-system = { path = "../nu-system", version = "0.95.0" } +nu-table = { path = "../nu-table", version = "0.95.0" } +nu-term-grid = { path = "../nu-term-grid", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } nu-ansi-term = { workspace = true } -nuon = { path = "../nuon", version = "0.94.3" } +nuon = { path = "../nuon", version = "0.95.0" } alphanumeric-sort = { workspace = true } base64 = { workspace = true } @@ -136,8 +136,8 @@ sqlite = ["rusqlite"] trash-support = ["trash"] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } dirs-next = { workspace = true } mockito = { workspace = true, default-features = false } @@ -145,4 +145,4 @@ quickcheck = { workspace = true } quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } pretty_assertions = { workspace = true } -tempfile = { workspace = true } +tempfile = { workspace = true } \ No newline at end of file diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index 45395b2ddb..37698ccaeb 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" -version = "0.94.3" +version = "0.95.0" [lib] proc-macro = true @@ -18,4 +18,4 @@ proc-macro2 = { workspace = true } syn = { workspace = true } quote = { workspace = true } proc-macro-error = { workspace = true } -convert_case = { workspace = true } +convert_case = { workspace = true } \ No newline at end of file diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index fae2b3eff4..7c815d4c72 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine" edition = "2021" license = "MIT" name = "nu-engine" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-glob = { path = "../nu-glob", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-glob = { path = "../nu-glob", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } [features] -plugin = [] +plugin = [] \ No newline at end of file diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index dc17e6087d..ac0fe732da 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore" edition = "2021" license = "MIT" name = "nu-explore" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-color-config = { path = "../nu-color-config", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-table = { path = "../nu-table", version = "0.94.3" } -nu-json = { path = "../nu-json", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-color-config = { path = "../nu-color-config", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-table = { path = "../nu-table", version = "0.95.0" } +nu-json = { path = "../nu-json", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } nu-ansi-term = { workspace = true } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.0" } anyhow = { workspace = true } log = { workspace = true } @@ -32,4 +32,4 @@ ansi-str = { workspace = true } unicode-width = { workspace = true } lscolors = { workspace = true, default-features = false, features = [ "nu-ansi-term", -] } +] } \ No newline at end of file diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index 3e0dbfbd12..b1386d1f7b 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-glob" -version = "0.94.3" +version = "0.95.0" authors = ["The Nushell Project Developers", "The Rust Project Developers"] license = "MIT/Apache-2.0" description = """ @@ -14,4 +14,4 @@ categories = ["filesystem"] bench = false [dev-dependencies] -doc-comment = "0.3" +doc-comment = "0.3" \ No newline at end of file diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 9d348d00d4..27db574785 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" license = "MIT" name = "nu-json" -version = "0.94.3" +version = "0.95.0" # 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.94.3" } -# serde_json = "1.0" +# nu-path = { path="../nu-path", version = "0.95.0" } +# serde_json = "1.0" \ No newline at end of file diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index c4e495f040..2384d46116 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -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.94.3" +version = "0.95.0" edition = "2021" license = "MIT" [dependencies] -nu-cli = { path = "../nu-cli", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } +nu-cli = { path = "../nu-cli", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } 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.94.3" } -nu-command = { path = "../nu-command", version = "0.94.3" } -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-command = { path = "../nu-command", version = "0.95.0" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } -assert-json-diff = "2.0" +assert-json-diff = "2.0" \ No newline at end of file diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 742009424a..5ca1a59d0c 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser" edition = "2021" license = "MIT" name = "nu-parser" -version = "0.94.3" +version = "0.95.0" exclude = ["/fuzz"] [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } @@ -27,4 +27,4 @@ serde_json = { workspace = true } rstest = { workspace = true, default-features = false } [features] -plugin = ["nu-plugin-engine"] +plugin = ["nu-plugin-engine"] \ No newline at end of file diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index e3c84acdb3..cf716b4571 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path" edition = "2021" license = "MIT" name = "nu-path" -version = "0.94.3" +version = "0.95.0" exclude = ["/fuzz"] [lib] @@ -18,4 +18,4 @@ dirs-next = { workspace = true } omnipath = { workspace = true } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies] -pwd = { workspace = true } +pwd = { workspace = true } \ No newline at end of file diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index 965b09d085..3e56d0f8e1 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core edition = "2021" license = "MIT" name = "nu-plugin-core" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3", default-features = false } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0", default-features = false } rmp-serde = { workspace = true } serde = { workspace = true } @@ -25,4 +25,4 @@ default = ["local-socket"] local-socket = ["interprocess", "nu-plugin-protocol/local-socket"] [target.'cfg(target_os = "windows")'.dependencies] -windows = { workspace = true } +windows = { workspace = true } \ No newline at end of file diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index 95c0298c09..5de92d1560 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi edition = "2021" license = "MIT" name = "nu-plugin-engine" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-system = { path = "../nu-system", version = "0.94.3" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-system = { path = "../nu-system", version = "0.95.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0", default-features = false } serde = { workspace = true } log = { workspace = true } @@ -31,4 +31,4 @@ local-socket = ["nu-plugin-core/local-socket"] windows = { workspace = true, features = [ # For setting process creation flags "Win32_System_Threading", -] } +] } \ No newline at end of file diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index 41c8a7c535..23bd524734 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot edition = "2021" license = "MIT" name = "nu-plugin-protocol" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-utils = { path = "../nu-utils", version = "0.95.0" } bincode = "1.3" serde = { workspace = true, features = ["derive"] } @@ -21,4 +21,4 @@ typetag = "0.2" [features] default = ["local-socket"] -local-socket = [] +local-socket = [] \ No newline at end of file diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index bc390d3994..b7220da898 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-plugin-test-support" -version = "0.94.3" +version = "0.95.0" edition = "2021" license = "MIT" description = "Testing support for Nushell plugins" @@ -12,17 +12,17 @@ bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3", features = ["plugin"] } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } -nu-parser = { path = "../nu-parser", version = "0.94.3", features = ["plugin"] } -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } +nu-engine = { path = "../nu-engine", version = "0.95.0", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-parser = { path = "../nu-parser", version = "0.95.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } nu-ansi-term = { workspace = true } similar = "2.5" [dev-dependencies] typetag = "0.2" -serde = "1.0" +serde = "1.0" \ No newline at end of file diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 68b3a7cadd..6f843cf264 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin" edition = "2021" license = "MIT" name = "nu-plugin" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0", default-features = false } log = { workspace = true } thiserror = "1.0" @@ -29,4 +29,4 @@ local-socket = ["nu-plugin-core/local-socket"] [target.'cfg(target_family = "unix")'.dependencies] # For setting the process group ID (EnterForeground / LeaveForeground) -nix = { workspace = true, default-features = false, features = ["process"] } +nix = { workspace = true, default-features = false, features = ["process"] } \ No newline at end of file diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index ae5be298aa..2b6a683295 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] doctest = false @@ -18,4 +18,4 @@ nu-ansi-term = { workspace = true } [dev-dependencies] heapless = { version = "0.8", default-features = false } -rand = "0.8" +rand = "0.8" \ No newline at end of file diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 849a968eb8..73637cf292 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol" edition = "2021" license = "MIT" name = "nu-protocol" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,10 +13,10 @@ version = "0.94.3" bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-system = { path = "../nu-system", version = "0.94.3" } -nu-derive-value = { path = "../nu-derive-value", version = "0.94.3" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-system = { path = "../nu-system", version = "0.95.0" } +nu-derive-value = { path = "../nu-derive-value", version = "0.95.0" } brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } @@ -47,11 +47,11 @@ plugin = [ serde_json = { workspace = true } strum = "0.26" strum_macros = "0.26" -nu-test-support = { path = "../nu-test-support", version = "0.94.3" } +nu-test-support = { path = "../nu-test-support", version = "0.95.0" } pretty_assertions = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } os_pipe = { workspace = true } [package.metadata.docs.rs] -all-features = true +all-features = true \ No newline at end of file diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index 660d418097..2617f4656d 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std" edition = "2021" license = "MIT" name = "nu-std" -version = "0.94.3" +version = "0.95.0" [dependencies] -nu-parser = { version = "0.94.3", path = "../nu-parser" } -nu-protocol = { version = "0.94.3", path = "../nu-protocol" } -nu-engine = { version = "0.94.3", path = "../nu-engine" } +nu-parser = { version = "0.95.0", path = "../nu-parser" } +nu-protocol = { version = "0.95.0", path = "../nu-protocol" } +nu-engine = { version = "0.95.0", path = "../nu-engine" } miette = { workspace = true, features = ["fancy-no-backtrace"] } -log = "0.4" +log = "0.4" \ No newline at end of file diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 3e888dba49..b9b23af6c3 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -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.94.3" +version = "0.95.0" edition = "2021" license = "MIT" @@ -45,4 +45,4 @@ windows = { workspace = true, features = [ "Win32_System_SystemInformation", "Win32_System_Threading", "Win32_UI_Shell", -]} +]} \ No newline at end of file diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index af7cf19f36..dcc4c294d6 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table" edition = "2021" license = "MIT" name = "nu-table" -version = "0.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-color-config = { path = "../nu-color-config", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-color-config = { path = "../nu-color-config", version = "0.95.0" } 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.94.3" } +# nu-test-support = { path="../nu-test-support", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index 47f6724b53..a577e6cb24 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } -unicode-width = { workspace = true } +unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index 86a1bf35e7..769ae44d00 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor edition = "2021" license = "MIT" name = "nu-test-support" -version = "0.94.3" +version = "0.95.0" [lib] doctest = false bench = false [dependencies] -nu-path = { path = "../nu-path", version = "0.94.3" } -nu-glob = { path = "../nu-glob", version = "0.94.3" } -nu-utils = { path = "../nu-utils", version = "0.94.3" } +nu-path = { path = "../nu-path", version = "0.95.0" } +nu-glob = { path = "../nu-glob", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.0" } num-format = { workspace = true } which = { workspace = true } -tempfile = { workspace = true } +tempfile = { workspace = true } \ No newline at end of file diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 0677f655d0..54821d0252 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-utils" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] @@ -29,4 +29,4 @@ unicase = "2.7.0" crossterm_winapi = "0.9" [target.'cfg(unix)'.dependencies] -nix = { workspace = true, default-features = false, features = ["user", "fs"] } +nix = { workspace = true, default-features = false, features = ["user", "fs"] } \ No newline at end of file diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 099adcd065..0980b487e2 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -1,6 +1,6 @@ # Nushell Config File # -# version = "0.94.3" +# version = "0.95.0" # For more information on defining custom themes, see # https://www.nushell.sh/book/coloring_and_theming.html @@ -888,4 +888,4 @@ $env.config = { event: { edit: selectall } } ] -} +} \ No newline at end of file diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index effe76c98a..e5c17e40de 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -1,6 +1,6 @@ # Nushell Environment Config File # -# version = "0.94.3" +# version = "0.95.0" def create_left_prompt [] { let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { @@ -98,4 +98,4 @@ $env.NU_PLUGIN_DIRS = [ # $env.PATH = ($env.PATH | uniq) # To load from a custom file you can use: -# source ($nu.default-config-dir | path join 'custom.nu') +# source ($nu.default-config-dir | path join 'custom.nu') \ No newline at end of file diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index 2791cdaa6f..0b8c899bb3 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,10 +10,10 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } serde = { workspace = true, default-features = false } typetag = "0.2" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.3" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index b2fd13abd9..9e5dc917f5 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -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.94.3" +version = "0.95.0" [[bin]] name = "nu_plugin_example" @@ -15,9 +15,9 @@ bench = false bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.3" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml index a649a5bc5a..a2c198bcb0 100644 --- a/crates/nu_plugin_formats/Cargo.toml +++ b/crates/nu_plugin_formats/Cargo.toml @@ -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.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } indexmap = { workspace = true } eml-parser = "0.1" @@ -18,4 +18,4 @@ ical = "0.11" rust-ini = "0.21.0" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.3" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } \ No newline at end of file diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index c356f3d7d3..15b601007a 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_gstat" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -git2 = "0.19" +git2 = "0.19" \ No newline at end of file diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 3701ad49bd..9f2b0e1122 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_inc" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } -semver = "1.0" +semver = "1.0" \ No newline at end of file diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index 4bfc066815..1fcf6d8eb4 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -6,7 +6,7 @@ # it also allows us to test the plugin interface with something manually implemented in a scripting # language without adding any extra dependencies to our tests. -const NUSHELL_VERSION = "0.94.3" +const NUSHELL_VERSION = "0.95.0" const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { @@ -265,4 +265,4 @@ def start_plugin [] { }) | each { from json | handle_input } | ignore -} +} \ No newline at end of file diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 98288f3ebf..e5e92822e0 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu_plugin_polars" repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_polars" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +17,9 @@ bench = false bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-path = { path = "../nu-path", version = "0.94.3" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.0" } # Potential dependencies for extras chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } @@ -73,9 +73,9 @@ optional = false version = "0.40" [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-command = { path = "../nu-command", version = "0.94.3" } -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.94.3" } -tempfile.workspace = true +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-command = { path = "../nu-command", version = "0.95.0" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } +tempfile.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index 80715abf11..14cd28a713 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -27,7 +27,7 @@ import sys import json -NUSHELL_VERSION = "0.94.3" +NUSHELL_VERSION = "0.95.0" PLUGIN_VERSION = "0.1.0" # bump if you change commands! @@ -258,4 +258,4 @@ if __name__ == "__main__": if len(sys.argv) == 2 and sys.argv[1] == "--stdio": plugin() else: - print("Run me from inside nushell!") + print("Run me from inside nushell!") \ No newline at end of file diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index 20a2a2729d..d044aeac42 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -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.94.3" +version = "0.95.0" [lib] doctest = false @@ -16,10 +16,10 @@ name = "nu_plugin_query" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } +nu-plugin = { path = "../nu-plugin", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } gjson = "0.8" scraper = { default-features = false, version = "0.19" } sxd-document = "0.3" -sxd-xpath = "0.4" +sxd-xpath = "0.4" \ No newline at end of file diff --git a/crates/nu_plugin_stress_internals/Cargo.toml b/crates/nu_plugin_stress_internals/Cargo.toml index 7103d99cda..e4e7f88403 100644 --- a/crates/nu_plugin_stress_internals/Cargo.toml +++ b/crates/nu_plugin_stress_internals/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_stre edition = "2021" license = "MIT" name = "nu_plugin_stress_internals" -version = "0.94.3" +version = "0.95.0" [[bin]] name = "nu_plugin_stress_internals" @@ -16,4 +16,4 @@ bench = false # assumptions about the serialized format serde = { workspace = true } serde_json = { workspace = true } -interprocess = { workspace = true } +interprocess = { workspace = true } \ No newline at end of file diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index 31c0cdd363..f977598450 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nuon" edition = "2021" license = "MIT" name = "nuon" -version = "0.94.3" +version = "0.95.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-parser = { path = "../nu-parser", version = "0.94.3" } -nu-protocol = { path = "../nu-protocol", version = "0.94.3" } -nu-engine = { path = "../nu-engine", version = "0.94.3" } +nu-parser = { path = "../nu-parser", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.0" } once_cell = { workspace = true } fancy-regex = { workspace = true } [dev-dependencies] -chrono = { workspace = true } +chrono = { workspace = true } \ No newline at end of file From 0dd35cddcd7d8e45f810ebf2de7ac1eb1c8d0042 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:26:07 -0700 Subject: [PATCH 23/38] Bumping version to 0.95.1 (#13231) Marks development for hotfix --- Cargo.toml | 40 +++++++++---------- crates/nu-cli/Cargo.toml | 24 +++++------ crates/nu-cmd-base/Cargo.toml | 10 ++--- crates/nu-cmd-extra/Cargo.toml | 22 +++++----- crates/nu-cmd-lang/Cargo.toml | 10 ++--- crates/nu-cmd-plugin/Cargo.toml | 10 ++--- crates/nu-color-config/Cargo.toml | 10 ++--- crates/nu-command/Cargo.toml | 34 ++++++++-------- crates/nu-derive-value/Cargo.toml | 2 +- crates/nu-engine/Cargo.toml | 10 ++--- crates/nu-explore/Cargo.toml | 18 ++++----- crates/nu-glob/Cargo.toml | 2 +- crates/nu-json/Cargo.toml | 4 +- crates/nu-lsp/Cargo.toml | 14 +++---- crates/nu-parser/Cargo.toml | 10 ++--- crates/nu-path/Cargo.toml | 2 +- crates/nu-plugin-core/Cargo.toml | 6 +-- crates/nu-plugin-engine/Cargo.toml | 12 +++--- crates/nu-plugin-protocol/Cargo.toml | 6 +-- crates/nu-plugin-test-support/Cargo.toml | 18 ++++----- crates/nu-plugin/Cargo.toml | 10 ++--- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 12 +++--- crates/nu-std/Cargo.toml | 8 ++-- crates/nu-system/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 12 +++--- crates/nu-term-grid/Cargo.toml | 4 +- crates/nu-test-support/Cargo.toml | 8 ++-- crates/nu-utils/Cargo.toml | 2 +- .../src/sample_config/default_config.nu | 2 +- .../nu-utils/src/sample_config/default_env.nu | 2 +- crates/nu_plugin_custom_values/Cargo.toml | 6 +-- crates/nu_plugin_example/Cargo.toml | 10 ++--- crates/nu_plugin_formats/Cargo.toml | 8 ++-- crates/nu_plugin_gstat/Cargo.toml | 6 +-- crates/nu_plugin_inc/Cargo.toml | 6 +-- .../nu_plugin_nu_example.nu | 2 +- crates/nu_plugin_polars/Cargo.toml | 18 ++++----- .../nu_plugin_python_example.py | 2 +- crates/nu_plugin_query/Cargo.toml | 6 +-- crates/nu_plugin_stress_internals/Cargo.toml | 2 +- crates/nuon/Cargo.toml | 8 ++-- 42 files changed, 201 insertions(+), 201 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 696c012b80..3e02e7e066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -180,22 +180,22 @@ windows = "0.54" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.95.0" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.0" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.0" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.0", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.0" } -nu-command = { path = "./crates/nu-command", version = "0.95.0" } -nu-engine = { path = "./crates/nu-engine", version = "0.95.0" } -nu-explore = { path = "./crates/nu-explore", version = "0.95.0" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.0" } -nu-parser = { path = "./crates/nu-parser", version = "0.95.0" } -nu-path = { path = "./crates/nu-path", version = "0.95.0" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.0" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.95.0" } -nu-std = { path = "./crates/nu-std", version = "0.95.0" } -nu-system = { path = "./crates/nu-system", version = "0.95.0" } -nu-utils = { path = "./crates/nu-utils", version = "0.95.0" } +nu-cli = { path = "./crates/nu-cli", version = "0.95.1" } +nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" } +nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true } +nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" } +nu-command = { path = "./crates/nu-command", version = "0.95.1" } +nu-engine = { path = "./crates/nu-engine", version = "0.95.1" } +nu-explore = { path = "./crates/nu-explore", version = "0.95.1" } +nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" } +nu-parser = { path = "./crates/nu-parser", version = "0.95.1" } +nu-path = { path = "./crates/nu-path", version = "0.95.1" } +nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" } +nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" } +nu-std = { path = "./crates/nu-std", version = "0.95.1" } +nu-system = { path = "./crates/nu-system", version = "0.95.1" } +nu-utils = { path = "./crates/nu-utils", version = "0.95.1" } reedline = { workspace = true, features = ["bashisms", "sqlite"] } @@ -225,9 +225,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.95.0" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.0" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.0" } +nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" } +nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" } +nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" } assert_cmd = "2.0" dirs-next = { workspace = true } tango-bench = "0.5" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 8e7c7b5535..2ed504d502 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli" edition = "2021" license = "MIT" name = "nu-cli" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } -nu-command = { path = "../nu-command", version = "0.95.0" } -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-command = { path = "../nu-command", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } rstest = { workspace = true, default-features = false } tempfile = { workspace = true } [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } -nu-color-config = { path = "../nu-color-config", version = "0.95.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-color-config = { path = "../nu-color-config", version = "0.95.1" } nu-ansi-term = { workspace = true } reedline = { workspace = true, features = ["bashisms", "sqlite"] } diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 773ee05414..2fe8610f49 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index 30c94420a3..8609adb44c 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,13 +13,13 @@ version = "0.95.0" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-json = { version = "0.95.0", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-pretty-hex = { version = "0.95.0", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-json = { version = "0.95.1", path = "../nu-json" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } # Potential dependencies for extras heck = { workspace = true } @@ -33,6 +33,6 @@ v_htmlescape = { workspace = true } itertools = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } -nu-command = { path = "../nu-command", version = "0.95.0" } -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } \ No newline at end of file +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-command = { path = "../nu-command", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 6db0d4c096..262248e6d9 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } itertools = { workspace = true } shadow-rs = { version = "0.28", default-features = false } diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index f6eccb753e..7d26fe5ddd 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" } itertools = { workspace = true } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 34f15cd74f..23af09432c 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi edition = "2021" license = "MIT" name = "nu-color-config" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-json = { path = "../nu-json", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-json = { path = "../nu-json", version = "0.95.1" } nu-ansi-term = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 355232b938..474742ae0a 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,21 +13,21 @@ version = "0.95.0" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.0" } -nu-color-config = { path = "../nu-color-config", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-glob = { path = "../nu-glob", version = "0.95.0" } -nu-json = { path = "../nu-json", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-system = { path = "../nu-system", version = "0.95.0" } -nu-table = { path = "../nu-table", version = "0.95.0" } -nu-term-grid = { path = "../nu-term-grid", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } +nu-color-config = { path = "../nu-color-config", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-glob = { path = "../nu-glob", version = "0.95.1" } +nu-json = { path = "../nu-json", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-system = { path = "../nu-system", version = "0.95.1" } +nu-table = { path = "../nu-table", version = "0.95.1" } +nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-ansi-term = { workspace = true } -nuon = { path = "../nuon", version = "0.95.0" } +nuon = { path = "../nuon", version = "0.95.1" } alphanumeric-sort = { workspace = true } base64 = { workspace = true } @@ -136,8 +136,8 @@ sqlite = ["rusqlite"] trash-support = ["trash"] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } dirs-next = { workspace = true } mockito = { workspace = true, default-features = false } diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index 37698ccaeb..50ca5d2407 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" -version = "0.95.0" +version = "0.95.1" [lib] proc-macro = true diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 7c815d4c72..3e6c3f787b 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine" edition = "2021" license = "MIT" name = "nu-engine" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-glob = { path = "../nu-glob", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-glob = { path = "../nu-glob", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } [features] plugin = [] \ No newline at end of file diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index ac0fe732da..6d0816ee3e 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore" edition = "2021" license = "MIT" name = "nu-explore" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-color-config = { path = "../nu-color-config", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-table = { path = "../nu-table", version = "0.95.0" } -nu-json = { path = "../nu-json", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-color-config = { path = "../nu-color-config", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-table = { path = "../nu-table", version = "0.95.1" } +nu-json = { path = "../nu-json", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } nu-ansi-term = { workspace = true } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.0" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } anyhow = { workspace = true } log = { workspace = true } diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index b1386d1f7b..932958910c 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-glob" -version = "0.95.0" +version = "0.95.1" authors = ["The Nushell Project Developers", "The Rust Project Developers"] license = "MIT/Apache-2.0" description = """ diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 27db574785..04c39bdac5 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" license = "MIT" name = "nu-json" -version = "0.95.0" +version = "0.95.1" # 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.95.0" } +# nu-path = { path="../nu-path", version = "0.95.1" } # serde_json = "1.0" \ No newline at end of file diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index 2384d46116..0f9ef01e69 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -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.95.0" +version = "0.95.1" edition = "2021" license = "MIT" [dependencies] -nu-cli = { path = "../nu-cli", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-cli = { path = "../nu-cli", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } 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.95.0" } -nu-command = { path = "../nu-command", version = "0.95.0" } -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-command = { path = "../nu-command", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } assert-json-diff = "2.0" \ No newline at end of file diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 5ca1a59d0c..b06e81387d 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser" edition = "2021" license = "MIT" name = "nu-parser" -version = "0.95.0" +version = "0.95.1" exclude = ["/fuzz"] [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index cf716b4571..dc2f870a9d 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path" edition = "2021" license = "MIT" name = "nu-path" -version = "0.95.0" +version = "0.95.1" exclude = ["/fuzz"] [lib] diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index 3e56d0f8e1..48aee13276 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core edition = "2021" license = "MIT" name = "nu-plugin-core" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0", default-features = false } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1", default-features = false } rmp-serde = { workspace = true } serde = { workspace = true } diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index 5de92d1560..f86c1ae703 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi edition = "2021" license = "MIT" name = "nu-plugin-engine" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-system = { path = "../nu-system", version = "0.95.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-system = { path = "../nu-system", version = "0.95.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } serde = { workspace = true } log = { workspace = true } diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index 23bd524734..939f164786 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot edition = "2021" license = "MIT" name = "nu-plugin-protocol" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-utils = { path = "../nu-utils", version = "0.95.1" } bincode = "1.3" serde = { workspace = true, features = ["derive"] } diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index b7220da898..efc76c3b93 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-plugin-test-support" -version = "0.95.0" +version = "0.95.1" edition = "2021" license = "MIT" description = "Testing support for Nushell plugins" @@ -12,14 +12,14 @@ bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0", features = ["plugin"] } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } -nu-parser = { path = "../nu-parser", version = "0.95.0", features = ["plugin"] } -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } +nu-engine = { path = "../nu-engine", version = "0.95.1", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-parser = { path = "../nu-parser", version = "0.95.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-ansi-term = { workspace = true } similar = "2.5" diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 6f843cf264..a3b4455d15 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin" edition = "2021" license = "MIT" name = "nu-plugin" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.0", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } log = { workspace = true } thiserror = "1.0" diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 2b6a683295..cd3c3502a4 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] doctest = false diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 73637cf292..ee0f5a8221 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol" edition = "2021" license = "MIT" name = "nu-protocol" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,10 +13,10 @@ version = "0.95.0" bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-system = { path = "../nu-system", version = "0.95.0" } -nu-derive-value = { path = "../nu-derive-value", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-system = { path = "../nu-system", version = "0.95.1" } +nu-derive-value = { path = "../nu-derive-value", version = "0.95.1" } brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } @@ -47,7 +47,7 @@ plugin = [ serde_json = { workspace = true } strum = "0.26" strum_macros = "0.26" -nu-test-support = { path = "../nu-test-support", version = "0.95.0" } +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } pretty_assertions = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index 2617f4656d..9ee4113e96 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std" edition = "2021" license = "MIT" name = "nu-std" -version = "0.95.0" +version = "0.95.1" [dependencies] -nu-parser = { version = "0.95.0", path = "../nu-parser" } -nu-protocol = { version = "0.95.0", path = "../nu-protocol" } -nu-engine = { version = "0.95.0", path = "../nu-engine" } +nu-parser = { version = "0.95.1", path = "../nu-parser" } +nu-protocol = { version = "0.95.1", path = "../nu-protocol" } +nu-engine = { version = "0.95.1", path = "../nu-engine" } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" \ No newline at end of file diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index b9b23af6c3..19ab1259fb 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -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.95.0" +version = "0.95.1" edition = "2021" license = "MIT" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index dcc4c294d6..9f00c4d1fd 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table" edition = "2021" license = "MIT" name = "nu-table" -version = "0.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-color-config = { path = "../nu-color-config", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-color-config = { path = "../nu-color-config", version = "0.95.1" } 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.95.0" } \ No newline at end of file +# nu-test-support = { path="../nu-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index a577e6cb24..0229f1f0f0 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index 769ae44d00..1b5f5df7ba 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] doctest = false bench = false [dependencies] -nu-path = { path = "../nu-path", version = "0.95.0" } -nu-glob = { path = "../nu-glob", version = "0.95.0" } -nu-utils = { path = "../nu-utils", version = "0.95.0" } +nu-path = { path = "../nu-path", version = "0.95.1" } +nu-glob = { path = "../nu-glob", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.95.1" } num-format = { workspace = true } which = { workspace = true } diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 54821d0252..60b4cb21aa 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-utils" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 0980b487e2..5856e385c4 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -1,6 +1,6 @@ # Nushell Config File # -# version = "0.95.0" +# version = "0.95.1" # For more information on defining custom themes, see # https://www.nushell.sh/book/coloring_and_theming.html diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index e5c17e40de..a686f6f99a 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -1,6 +1,6 @@ # Nushell Environment Config File # -# version = "0.95.0" +# version = "0.95.1" def create_left_prompt [] { let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index 0b8c899bb3..e5f8951429 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,10 +10,10 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } serde = { workspace = true, default-features = false } typetag = "0.2" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 9e5dc917f5..08ba3e1566 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -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.95.0" +version = "0.95.1" [[bin]] name = "nu_plugin_example" @@ -15,9 +15,9 @@ bench = false bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml index a2c198bcb0..29e8953f24 100644 --- a/crates/nu_plugin_formats/Cargo.toml +++ b/crates/nu_plugin_formats/Cargo.toml @@ -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.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } indexmap = { workspace = true } eml-parser = "0.1" @@ -18,4 +18,4 @@ ical = "0.11" rust-ini = "0.21.0" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index 15b601007a..da1c13a88e 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_gstat" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } git2 = "0.19" \ No newline at end of file diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 9f2b0e1122..0479a47843 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_inc" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } semver = "1.0" \ No newline at end of file diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index 1fcf6d8eb4..d19cd36c14 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -6,7 +6,7 @@ # it also allows us to test the plugin interface with something manually implemented in a scripting # language without adding any extra dependencies to our tests. -const NUSHELL_VERSION = "0.95.0" +const NUSHELL_VERSION = "0.95.1" const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index e5e92822e0..77bb84e988 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu_plugin_polars" repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_polars" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +17,9 @@ bench = false bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-path = { path = "../nu-path", version = "0.95.0" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } # Potential dependencies for extras chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } @@ -73,9 +73,9 @@ optional = false version = "0.40" [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-command = { path = "../nu-command", version = "0.95.0" } -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-command = { path = "../nu-command", version = "0.95.1" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } tempfile.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index 14cd28a713..34e54fea71 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -27,7 +27,7 @@ import sys import json -NUSHELL_VERSION = "0.95.0" +NUSHELL_VERSION = "0.95.1" PLUGIN_VERSION = "0.1.0" # bump if you change commands! diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index d044aeac42..4a54e9bf60 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -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.95.0" +version = "0.95.1" [lib] doctest = false @@ -16,8 +16,8 @@ name = "nu_plugin_query" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } +nu-plugin = { path = "../nu-plugin", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } gjson = "0.8" scraper = { default-features = false, version = "0.19" } diff --git a/crates/nu_plugin_stress_internals/Cargo.toml b/crates/nu_plugin_stress_internals/Cargo.toml index e4e7f88403..2a53eb6749 100644 --- a/crates/nu_plugin_stress_internals/Cargo.toml +++ b/crates/nu_plugin_stress_internals/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_stre edition = "2021" license = "MIT" name = "nu_plugin_stress_internals" -version = "0.95.0" +version = "0.95.1" [[bin]] name = "nu_plugin_stress_internals" diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index f977598450..bbb2dfe73c 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nuon" edition = "2021" license = "MIT" name = "nuon" -version = "0.95.0" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-parser = { path = "../nu-parser", version = "0.95.0" } -nu-protocol = { path = "../nu-protocol", version = "0.95.0" } -nu-engine = { path = "../nu-engine", version = "0.95.0" } +nu-parser = { path = "../nu-parser", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.95.1" } once_cell = { workspace = true } fancy-regex = { workspace = true } From f241110005006f10e16c6080ebab7abec6f3e5ce Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 25 Jun 2024 20:31:54 -0500 Subject: [PATCH 24/38] implement autoloading (#13217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR implements script or module autoloading. It does this by finding the `$nu.vendor-autoload-dir`, lists the contents and sorts them by file name. These files are evaluated in that order. To see what's going on, you can use `--log-level warn` ``` ❯ cargo r -- --log-level warn Finished dev [unoptimized + debuginfo] target(s) in 0.58s Running `target\debug\nu.exe --log-level warn` 2024-06-24 09:23:20.494 PM [WARN ] nu::config_files: set_config_path() cwd: "C:\\Users\\fdncred\\source\\repos\\nushell", default_config: config.nu, key: config-path, config_file_specified: None 2024-06-24 09:23:20.495 PM [WARN ] nu::config_files: set_config_path() cwd: "C:\\Users\\fdncred\\source\\repos\\nushell", default_config: env.nu, key: env-path, config_file_specified: None 2024-06-24 09:23:20.629 PM [WARN ] nu::config_files: setup_config() config_file_specified: None, env_file_specified: None, login: false 2024-06-24 09:23:20.660 PM [WARN ] nu::config_files: read_config_file() config_file_specified: None, is_env_config: true Hello, from env.nu 2024-06-24 09:23:20.679 PM [WARN ] nu::config_files: read_config_file() config_file_specified: None, is_env_config: false Hello, from config.nu Hello, from defs.nu Activating Microsoft Visual Studio environment. 2024-06-24 09:23:21.398 PM [WARN ] nu::config_files: read_vendor_autoload_files() src\config_files.rs:234:9 2024-06-24 09:23:21.399 PM [WARN ] nu::config_files: read_vendor_autoload_files: C:\ProgramData\nushell\vendor\autoload 2024-06-24 09:23:21.399 PM [WARN ] nu::config_files: AutoLoading: "C:\\ProgramData\\nushell\\vendor\\autoload\\01_get-weather.nu" 2024-06-24 09:23:21.675 PM [WARN ] nu::config_files: AutoLoading: "C:\\ProgramData\\nushell\\vendor\\autoload\\02_temp.nu" 2024-06-24 09:23:21.817 PM [WARN ] nu_cli::repl: Terminal doesn't support use_kitty_protocol config ``` # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-protocol/src/eval_const.rs | 65 +++++++++++++++++----------- src/config_files.rs | 45 ++++++++++++++++++- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 8f6382ae44..87913e4ee3 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -194,31 +194,11 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu // if not, use the default /usr/share/nushell/vendor/autoload // check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default - Value::string( - option_env!("NU_VENDOR_AUTOLOAD_DIR") - .map(String::from) - .unwrap_or_else(|| { - if cfg!(windows) { - let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") { - Some(v) => format!( - "{}\\nushell\\vendor\\autoload", - v.coerce_string().unwrap_or("C:\\ProgramData".into()) - ), - None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(), - }; - all_user_profile - } else { - // In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set - // check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload - // otherwise default to /usr/share/nushell/vendor/autoload - option_env!("PREFIX").map(String::from).map_or_else( - || "/usr/local/share/nushell/vendor/autoload".into(), - |prefix| format!("{}/share/nushell/vendor/autoload", prefix), - ) - } - }), - span, - ), + if let Some(path) = get_vendor_autoload_dir(engine_state) { + Value::string(path.to_string_lossy(), span) + } else { + Value::error(ShellError::ConfigDirNotFound { span: Some(span) }, span) + }, ); record.push("temp-path", { @@ -275,6 +255,41 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu Value::record(record, span) } +pub fn get_vendor_autoload_dir(engine_state: &EngineState) -> Option { + // pseudo code + // if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it + // if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload + // if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload + // if not, use the default /usr/share/nushell/vendor/autoload + + // check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default + Some( + option_env!("NU_VENDOR_AUTOLOAD_DIR") + .map(String::from) + .unwrap_or_else(|| { + if cfg!(windows) { + let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") { + Some(v) => format!( + "{}\\nushell\\vendor\\autoload", + v.coerce_string().unwrap_or("C:\\ProgramData".into()) + ), + None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(), + }; + all_user_profile + } else { + // In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set + // check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload + // otherwise default to /usr/share/nushell/vendor/autoload + option_env!("PREFIX").map(String::from).map_or_else( + || "/usr/local/share/nushell/vendor/autoload".into(), + |prefix| format!("{}/share/nushell/vendor/autoload", prefix), + ) + } + }) + .into(), + ) +} + fn eval_const_call( working_set: &StateWorkingSet, call: &Call, diff --git a/src/config_files.rs b/src/config_files.rs index ec4511860f..30977a6d0e 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -9,8 +9,9 @@ use nu_protocol::{ }; use nu_utils::{get_default_config, get_default_env}; use std::{ + fs, fs::File, - io::Write, + io::{Result, Write}, panic::{catch_unwind, AssertUnwindSafe}, path::Path, sync::Arc, @@ -176,6 +177,46 @@ pub(crate) fn read_default_env_file(engine_state: &mut EngineState, stack: &mut } } +fn read_and_sort_directory(path: &Path) -> Result> { + let mut entries = Vec::new(); + + for entry in fs::read_dir(path)? { + let entry = entry?; + let file_name = entry.file_name(); + let file_name_str = file_name.into_string().unwrap_or_default(); + entries.push(file_name_str); + } + + entries.sort(); + + Ok(entries) +} + +pub(crate) fn read_vendor_autoload_files(engine_state: &mut EngineState, stack: &mut Stack) { + warn!( + "read_vendor_autoload_files() {}:{}:{}", + file!(), + line!(), + column!() + ); + + // read and source vendor_autoload_files file if exists + if let Some(autoload_dir) = nu_protocol::eval_const::get_vendor_autoload_dir(engine_state) { + warn!("read_vendor_autoload_files: {}", autoload_dir.display()); + + if autoload_dir.exists() { + let entries = read_and_sort_directory(&autoload_dir); + if let Ok(entries) = entries { + for entry in entries { + let path = autoload_dir.join(entry); + warn!("AutoLoading: {:?}", path); + eval_config_contents(path, engine_state, stack); + } + } + } + } +} + fn eval_default_config( engine_state: &mut EngineState, stack: &mut Stack, @@ -236,6 +277,8 @@ pub(crate) fn setup_config( if is_login_shell { read_loginshell_file(engine_state, stack); } + // read and auto load vendor autoload files + read_vendor_autoload_files(engine_state, stack); })); if result.is_err() { eprintln!( From 55ee47630639ad6032dc7ba98bd956039595600e Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Wed, 26 Jun 2024 01:32:54 +0000 Subject: [PATCH 25/38] Define keywords (#13213) # Description Some commands in `nu-cmd-lang` are not classified as keywords even though they should be. # User-Facing Changes In the output of `which`, `scope commands`, and `help commands`, some commands will now have a `type` of `keyword` instead of `built-in`. --- .../nu-cmd-lang/src/core_commands/break_.rs | 10 +++++ .../src/core_commands/continue_.rs | 9 ++++ crates/nu-cmd-lang/src/core_commands/if_.rs | 11 ++++- crates/nu-cmd-lang/src/core_commands/loop_.rs | 10 +++++ .../nu-cmd-lang/src/core_commands/match_.rs | 11 ++++- .../src/core_commands/scope/command.rs | 5 --- crates/nu-cmd-lang/src/core_commands/try_.rs | 11 ++++- .../nu-cmd-lang/src/core_commands/while_.rs | 10 +++++ crates/nu-command/src/env/export_env.rs | 10 +++++ crates/nu-command/src/env/source_env.rs | 10 +++++ crates/nu-command/src/filters/where_.rs | 6 ++- crates/nu-parser/src/parse_keywords.rs | 42 ++++++++++++------- crates/nu-parser/src/parser.rs | 13 +++--- 13 files changed, 126 insertions(+), 32 deletions(-) diff --git a/crates/nu-cmd-lang/src/core_commands/break_.rs b/crates/nu-cmd-lang/src/core_commands/break_.rs index 4698f12c34..90cc1a73f2 100644 --- a/crates/nu-cmd-lang/src/core_commands/break_.rs +++ b/crates/nu-cmd-lang/src/core_commands/break_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct Break; @@ -18,6 +19,15 @@ impl Command for Break { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, _engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/continue_.rs b/crates/nu-cmd-lang/src/core_commands/continue_.rs index f65a983269..cfa3e38335 100644 --- a/crates/nu-cmd-lang/src/core_commands/continue_.rs +++ b/crates/nu-cmd-lang/src/core_commands/continue_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct Continue; @@ -18,6 +19,14 @@ impl Command for Continue { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } fn run( &self, _engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs index a2071530ac..738d901759 100644 --- a/crates/nu-cmd-lang/src/core_commands/if_.rs +++ b/crates/nu-cmd-lang/src/core_commands/if_.rs @@ -2,7 +2,7 @@ use nu_engine::{ command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input, }; use nu_protocol::{ - engine::StateWorkingSet, + engine::{CommandType, StateWorkingSet}, eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input}, }; @@ -41,6 +41,15 @@ impl Command for If { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn is_const(&self) -> bool { true } diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index 9b1e36a057..a9c642ca3c 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -1,4 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block}; +use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct Loop; @@ -20,6 +21,15 @@ impl Command for Loop { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/match_.rs b/crates/nu-cmd-lang/src/core_commands/match_.rs index 41b5c24702..d28a59cbad 100644 --- a/crates/nu-cmd-lang/src/core_commands/match_.rs +++ b/crates/nu-cmd-lang/src/core_commands/match_.rs @@ -1,7 +1,7 @@ use nu_engine::{ command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input, }; -use nu_protocol::engine::Matcher; +use nu_protocol::engine::{CommandType, Matcher}; #[derive(Clone)] pub struct Match; @@ -27,6 +27,15 @@ impl Command for Match { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/scope/command.rs b/crates/nu-cmd-lang/src/core_commands/scope/command.rs index 98439226cf..cc52a8a16f 100644 --- a/crates/nu-cmd-lang/src/core_commands/scope/command.rs +++ b/crates/nu-cmd-lang/src/core_commands/scope/command.rs @@ -1,5 +1,4 @@ use nu_engine::{command_prelude::*, get_full_help}; -use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct Scope; @@ -20,10 +19,6 @@ impl Command for Scope { "Commands for getting info about what is in scope." } - fn command_type(&self) -> CommandType { - CommandType::Keyword - } - fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index 1f16f97908..f99825b88d 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -1,5 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn}; -use nu_protocol::engine::Closure; +use nu_protocol::engine::{Closure, CommandType}; #[derive(Clone)] pub struct Try; @@ -31,6 +31,15 @@ impl Command for Try { .category(Category::Core) } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index bf9076aa0c..646b95c82e 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -1,4 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; +use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct While; @@ -29,6 +30,15 @@ impl Command for While { vec!["loop"] } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-command/src/env/export_env.rs b/crates/nu-command/src/env/export_env.rs index 20605a9bb5..00f2c73ef4 100644 --- a/crates/nu-command/src/env/export_env.rs +++ b/crates/nu-command/src/env/export_env.rs @@ -1,4 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block, redirect_env}; +use nu_protocol::engine::CommandType; #[derive(Clone)] pub struct ExportEnv; @@ -23,6 +24,15 @@ impl Command for ExportEnv { "Run a block and preserve its environment in a current scope." } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 71c1e6dc3f..0d8b118e8d 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -2,6 +2,7 @@ use nu_engine::{ command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return, redirect_env, }; +use nu_protocol::engine::CommandType; use std::path::PathBuf; /// Source a file for environment variables. @@ -28,6 +29,15 @@ impl Command for SourceEnv { "Source the environment from a source file into the current environment." } + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nu.html"# + } + + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn run( &self, engine_state: &EngineState, diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index fe73de354f..922879d09b 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,5 +1,5 @@ use nu_engine::{command_prelude::*, ClosureEval}; -use nu_protocol::engine::Closure; +use nu_protocol::engine::{Closure, CommandType}; #[derive(Clone)] pub struct Where; @@ -19,6 +19,10 @@ tables, known as "row conditions". On the other hand, reading the condition from not supported."# } + fn command_type(&self) -> CommandType { + CommandType::Keyword + } + fn signature(&self) -> nu_protocol::Signature { Signature::build("where") .input_output_types(vec![ diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 673a59fb05..6d3862db37 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -42,32 +42,44 @@ use crate::{ }; /// These parser keywords can be aliased -pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[b"overlay hide", b"overlay new", b"overlay use"]; +pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[ + b"if", + b"match", + b"try", + b"overlay", + b"overlay hide", + b"overlay new", + b"overlay use", +]; pub const RESERVED_VARIABLE_NAMES: [&str; 3] = ["in", "nu", "env"]; /// These parser keywords cannot be aliased (either not possible, or support not yet added) pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[ - b"export", - b"def", - b"export def", - b"for", - b"extern", - b"export extern", b"alias", - b"export alias", - b"export-env", + b"const", + b"def", + b"extern", b"module", b"use", + b"export", + b"export alias", + b"export const", + b"export def", + b"export extern", + b"export module", b"export use", - b"hide", - // b"overlay", - // b"overlay hide", - // b"overlay new", - // b"overlay use", + b"for", + b"loop", + b"while", + b"return", + b"break", + b"continue", b"let", - b"const", b"mut", + b"hide", + b"export-env", + b"source-env", b"source", b"where", b"register", diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index ba718cc771..1fef8bc133 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -934,15 +934,12 @@ pub fn parse_internal_call( let output = signature.get_output_type(); // storing the var ID for later due to borrowing issues - let lib_dirs_var_id = if decl.is_builtin() { - match decl.name() { - "use" | "overlay use" | "source-env" | "nu-check" => { - find_dirs_var(working_set, LIB_DIRS_VAR) - } - _ => None, + let lib_dirs_var_id = match decl.name() { + "use" | "overlay use" | "source-env" if decl.is_keyword() => { + find_dirs_var(working_set, LIB_DIRS_VAR) } - } else { - None + "nu-check" if decl.is_builtin() => find_dirs_var(working_set, LIB_DIRS_VAR), + _ => None, }; // The index into the positional parameter in the definition From def36865effde927b919c3221f1ee44b05798770 Mon Sep 17 00:00:00 2001 From: Wind Date: Wed, 26 Jun 2024 09:33:37 +0800 Subject: [PATCH 26/38] Enable reloading changes to a submodule (#13170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: https://github.com/nushell/nushell/issues/12099 Currently if user run `use voice.nu`, and file is unchanged, then run `use voice.nu` again. nushell will use the module directly, even if submodule inside `voice.nu` is changed. After discussed with @kubouch, I think it's ok to re-parse the module file when: 1. It exports sub modules which are defined by a file 2. It uses other modules which are defined by a file ## About the change: To achieve the behavior, we need to add 2 attributes to `Module`: 1. `imported_modules`: it tracks the other modules is imported by the givem `module`, e.g: `use foo.nu` 2. `file`: the path of a module, if a module is defined by a file, it will be `Some(path)`, or else it will be `None`. After the change: use voice.nu always read the file and parse it. use voice will still use the module which is saved in EngineState. # User-Facing Changes use `xxx.nu` will read the file and parse it if it exports submodules or uses submodules # Tests + Formatting Done --------- Co-authored-by: Jakub Žádník --- crates/nu-parser/src/lib.rs | 3 +- crates/nu-parser/src/parse_keywords.rs | 71 +++++++- crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/lib.rs | 1 + crates/nu-protocol/src/module.rs | 35 +++- .../src/parser_path.rs | 2 +- tests/modules/mod.rs | 161 +++++++++++++++++- 7 files changed, 258 insertions(+), 17 deletions(-) rename crates/{nu-parser => nu-protocol}/src/parser_path.rs (98%) diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index eeb6a590b8..89cba8d243 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -8,7 +8,6 @@ mod parse_keywords; mod parse_patterns; mod parse_shape_specs; mod parser; -mod parser_path; mod type_check; pub use deparse::{escape_for_script_arg, escape_quote_string}; @@ -18,8 +17,8 @@ pub use flatten::{ pub use known_external::KnownExternal; pub use lex::{lex, lex_signature, Token, TokenContents}; pub use lite_parser::{lite_parse, LiteBlock, LiteCommand}; +pub use nu_protocol::parser_path::*; pub use parse_keywords::*; -pub use parser_path::*; pub use parser::{ is_math_expression_like, parse, parse_block, parse_expression, parse_external_call, diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 6d3862db37..70d217c564 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -2,7 +2,6 @@ use crate::{ exportable::Exportable, parse_block, parser::{parse_redirection, redirecting_builtin_error}, - parser_path::ParserPath, type_check::{check_block_input_output, type_compatible}, }; use itertools::Itertools; @@ -15,6 +14,7 @@ use nu_protocol::{ }, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, eval_const::eval_constant, + parser_path::ParserPath, Alias, BlockId, DeclId, Module, ModuleId, ParseError, PositionalArg, ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, Value, VarId, }; @@ -1204,7 +1204,7 @@ pub fn parse_export_in_block( "export alias" => parse_alias(working_set, lite_command, None), "export def" => parse_def(working_set, lite_command, None).0, "export const" => parse_const(working_set, &lite_command.parts[1..]), - "export use" => parse_use(working_set, lite_command).0, + "export use" => parse_use(working_set, lite_command, None).0, "export module" => parse_module(working_set, lite_command, None).0, "export extern" => parse_extern(working_set, lite_command, None), _ => { @@ -1223,6 +1223,7 @@ pub fn parse_export_in_module( working_set: &mut StateWorkingSet, lite_command: &LiteCommand, module_name: &[u8], + parent_module: &mut Module, ) -> (Pipeline, Vec) { let spans = &lite_command.parts[..]; @@ -1428,7 +1429,8 @@ pub fn parse_export_in_module( pipe: lite_command.pipe, redirection: lite_command.redirection.clone(), }; - let (pipeline, exportables) = parse_use(working_set, &lite_command); + let (pipeline, exportables) = + parse_use(working_set, &lite_command, Some(parent_module)); let export_use_decl_id = if let Some(id) = working_set.find_decl(b"export use") { id @@ -1771,7 +1773,7 @@ pub fn parse_module_block( )) } b"use" => { - let (pipeline, _) = parse_use(working_set, command); + let (pipeline, _) = parse_use(working_set, command, Some(&mut module)); block.pipelines.push(pipeline) } @@ -1786,7 +1788,7 @@ pub fn parse_module_block( } b"export" => { let (pipe, exportables) = - parse_export_in_module(working_set, command, module_name); + parse_export_in_module(working_set, command, module_name, &mut module); for exportable in exportables { match exportable { @@ -1896,6 +1898,48 @@ pub fn parse_module_block( (block, module, module_comments) } +fn module_needs_reloading(working_set: &StateWorkingSet, module_id: ModuleId) -> bool { + let module = working_set.get_module(module_id); + + fn submodule_need_reloading(working_set: &StateWorkingSet, submodule_id: ModuleId) -> bool { + let submodule = working_set.get_module(submodule_id); + let submodule_changed = if let Some((file_path, file_id)) = &submodule.file { + let existing_contents = working_set.get_contents_of_file(*file_id); + let file_contents = file_path.read(working_set); + + if let (Some(existing), Some(new)) = (existing_contents, file_contents) { + existing != new + } else { + false + } + } else { + false + }; + + if submodule_changed { + true + } else { + module_needs_reloading(working_set, submodule_id) + } + } + + let export_submodule_changed = module + .submodules + .iter() + .any(|(_, submodule_id)| submodule_need_reloading(working_set, *submodule_id)); + + if export_submodule_changed { + return true; + } + + let private_submodule_changed = module + .imported_modules + .iter() + .any(|submodule_id| submodule_need_reloading(working_set, *submodule_id)); + + private_submodule_changed +} + /// Parse a module from a file. /// /// The module name is inferred from the stem of the file, unless specified in `name_override`. @@ -1934,23 +1978,26 @@ fn parse_module_file( // Check if we've parsed the module before. if let Some(module_id) = working_set.find_module_by_span(new_span) { - return Some(module_id); + if !module_needs_reloading(working_set, module_id) { + return Some(module_id); + } } // Add the file to the stack of files being processed. - if let Err(e) = working_set.files.push(path.path_buf(), path_span) { + if let Err(e) = working_set.files.push(path.clone().path_buf(), path_span) { working_set.error(e); return None; } // Parse the module - let (block, module, module_comments) = + let (block, mut module, module_comments) = parse_module_block(working_set, new_span, module_name.as_bytes()); // Remove the file from the stack of files being processed. working_set.files.pop(); let _ = working_set.add_block(Arc::new(block)); + module.file = Some((path, file_id)); let module_id = working_set.add_module(&module_name, module, module_comments); Some(module_id) @@ -2240,6 +2287,7 @@ pub fn parse_module( pub fn parse_use( working_set: &mut StateWorkingSet, lite_command: &LiteCommand, + parent_module: Option<&mut Module>, ) -> (Pipeline, Vec) { let spans = &lite_command.parts; @@ -2385,12 +2433,14 @@ pub fn parse_use( ); }; + let mut imported_modules = vec![]; let (definitions, errors) = module.resolve_import_pattern( working_set, module_id, &import_pattern.members, None, name_span, + &mut imported_modules, ); working_set.parse_errors.extend(errors); @@ -2432,6 +2482,9 @@ pub fn parse_use( import_pattern.constants = constants.iter().map(|(_, id)| *id).collect(); + if let Some(m) = parent_module { + m.track_imported_modules(&imported_modules) + } // Extend the current scope with the module's exportables working_set.use_decls(definitions.decls); working_set.use_modules(definitions.modules); @@ -2865,6 +2918,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box) -> &[], Some(final_overlay_name.as_bytes()), call.head, + &mut vec![], ) } else { origin_module.resolve_import_pattern( @@ -2875,6 +2929,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box) -> }], Some(final_overlay_name.as_bytes()), call.head, + &mut vec![], ) } } else { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1fef8bc133..f6bbd84f6c 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -5377,7 +5377,7 @@ pub fn parse_builtin_commands( } b"alias" => parse_alias(working_set, lite_command, None), b"module" => parse_module(working_set, lite_command, None).0, - b"use" => parse_use(working_set, lite_command).0, + b"use" => parse_use(working_set, lite_command, None).0, b"overlay" => { if let Some(redirection) = lite_command.redirection.as_ref() { working_set.error(redirecting_builtin_error("overlay", redirection)); diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 78e7d40680..9c176953d5 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -11,6 +11,7 @@ mod example; mod id; mod lev_distance; mod module; +pub mod parser_path; mod pipeline; #[cfg(feature = "plugin")] mod plugin; diff --git a/crates/nu-protocol/src/module.rs b/crates/nu-protocol/src/module.rs index ebb6f5621e..6b4aec0c60 100644 --- a/crates/nu-protocol/src/module.rs +++ b/crates/nu-protocol/src/module.rs @@ -1,8 +1,9 @@ use crate::{ - ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span, - Value, VarId, + ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, FileId, ModuleId, + ParseError, Span, Value, VarId, }; +use crate::parser_path::ParserPath; use indexmap::IndexMap; pub struct ResolvedImportPattern { @@ -35,6 +36,8 @@ pub struct Module { pub env_block: Option, // `export-env { ... }` block pub main: Option, // `export def main` pub span: Option, + pub imported_modules: Vec, // use other_module.nu + pub file: Option<(ParserPath, FileId)>, } impl Module { @@ -47,6 +50,8 @@ impl Module { env_block: None, main: None, span: None, + imported_modules: vec![], + file: None, } } @@ -59,6 +64,8 @@ impl Module { env_block: None, main: None, span: Some(span), + imported_modules: vec![], + file: None, } } @@ -82,6 +89,12 @@ impl Module { self.env_block = Some(block_id); } + pub fn track_imported_modules(&mut self, module_id: &[ModuleId]) { + for m in module_id { + self.imported_modules.push(*m) + } + } + pub fn has_decl(&self, name: &[u8]) -> bool { if name == self.name && self.main.is_some() { return true; @@ -90,6 +103,9 @@ impl Module { self.decls.contains_key(name) } + /// Resolve `members` from given module, which is indicated by `self_id` to import. + /// + /// When resolving, all modules are recorded in `imported_modules`. pub fn resolve_import_pattern( &self, working_set: &StateWorkingSet, @@ -97,7 +113,9 @@ impl Module { members: &[ImportPatternMember], name_override: Option<&[u8]>, // name under the module was stored (doesn't have to be the same as self.name) backup_span: Span, + imported_modules: &mut Vec, ) -> (ResolvedImportPattern, Vec) { + imported_modules.push(self_id); let final_name = name_override.unwrap_or(&self.name).to_vec(); let (head, rest) = if let Some((head, rest)) = members.split_first() { @@ -112,8 +130,14 @@ impl Module { let submodule = working_set.get_module(*id); let span = submodule.span.or(self.span).unwrap_or(backup_span); - let (sub_results, sub_errors) = - submodule.resolve_import_pattern(working_set, *id, &[], None, span); + let (sub_results, sub_errors) = submodule.resolve_import_pattern( + working_set, + *id, + &[], + None, + span, + imported_modules, + ); errors.extend(sub_errors); for (sub_name, sub_decl_id) in sub_results.decls { @@ -212,6 +236,7 @@ impl Module { rest, None, self.span.unwrap_or(backup_span), + imported_modules, ) } else { ( @@ -234,6 +259,7 @@ impl Module { &[], None, self.span.unwrap_or(backup_span), + imported_modules, ); decls.extend(sub_results.decls); @@ -287,6 +313,7 @@ impl Module { rest, None, self.span.unwrap_or(backup_span), + imported_modules, ); decls.extend(sub_results.decls); diff --git a/crates/nu-parser/src/parser_path.rs b/crates/nu-protocol/src/parser_path.rs similarity index 98% rename from crates/nu-parser/src/parser_path.rs rename to crates/nu-protocol/src/parser_path.rs index 2d0fbce2a2..4935d9fbe2 100644 --- a/crates/nu-parser/src/parser_path.rs +++ b/crates/nu-protocol/src/parser_path.rs @@ -1,4 +1,4 @@ -use nu_protocol::engine::{StateWorkingSet, VirtualPath}; +use crate::engine::{StateWorkingSet, VirtualPath}; use std::{ ffi::OsStr, path::{Path, PathBuf}, diff --git a/tests/modules/mod.rs b/tests/modules/mod.rs index c556d6fa7a..9aa1282f21 100644 --- a/tests/modules/mod.rs +++ b/tests/modules/mod.rs @@ -1,4 +1,4 @@ -use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::fs::Stub::{FileWithContent, FileWithContentToBeTrimmed}; use nu_test_support::playground::Playground; use nu_test_support::{nu, nu_repl_code}; use pretty_assertions::assert_eq; @@ -760,3 +760,162 @@ fn nested_list_export_works() { let actual = nu!(&inp.join("; ")); assert_eq!(actual.out, "bacon"); } + +#[test] +fn reload_submodules() { + Playground::setup("reload_submodule_changed_file", |dirs, sandbox| { + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export module animals.nu"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + // should also verify something unchanged if `use voice`. + let inp = [ + "use voice.nu", + r#""export def cat [] {'meow'}" | save -f animals.nu"#, + "use voice", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + // should also works if we use members directly. + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export module animals.nu"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu animals cat", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu animals cat", + "(cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + }); +} + +#[test] +fn use_submodules() { + Playground::setup("use_submodules", |dirs, sandbox| { + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + // should also verify something unchanged if `use voice`. + let inp = [ + "use voice.nu", + r#""export def cat [] {'meow'}" | save -f animals.nu"#, + "use voice", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + // also verify something is changed when using members. + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu cat"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu", + "(voice cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu *"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu", + "(voice cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu [cat]"#), + FileWithContent("animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f animals.nu"#, + "use voice.nu", + "(voice cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + }); +} + +#[test] +fn use_nested_submodules() { + Playground::setup("use_submodules", |dirs, sandbox| { + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu"#), + FileWithContent("animals.nu", r#"export use nested_animals.nu"#), + FileWithContent("nested_animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f nested_animals.nu"#, + "use voice.nu", + "(voice animals nested_animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + sandbox.with_files(&[ + FileWithContent("voice.nu", r#"export use animals.nu"#), + FileWithContent("animals.nu", r#"export use nested_animals.nu cat"#), + FileWithContent("nested_animals.nu", "export def cat [] { 'meow'}"), + ]); + let inp = [ + "use voice.nu", + r#""export def cat [] {'woem'}" | save -f nested_animals.nu"#, + "use voice.nu", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + + sandbox.with_files(&[ + FileWithContent("animals.nu", r#"export use nested_animals.nu cat"#), + FileWithContent("nested_animals.nu", "export def cat [] { 'meow' }"), + ]); + let inp = [ + "module voice { export module animals.nu }", + "use voice", + r#""export def cat [] {'woem'}" | save -f nested_animals.nu"#, + "use voice.nu", + "(voice animals cat) == 'woem'", + ]; + let actual = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual.out, "true"); + }) +} From 5a486029dbe2d33a690d325a6cc59639f07eb74f Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Wed, 26 Jun 2024 01:33:57 +0000 Subject: [PATCH 27/38] Add typed path forms (#13115) # Description This PR adds new types to `nu-path` to enforce path invariants. Namely, this PR adds: - `Path` and `PathBuf`. These types are different from, but analogous to `std::path::Path` and `std::path::PathBuf`. - `RelativePath` and `RelativePathBuf`. These types must be/contain strictly relative paths. - `AbsolutePath` and `AbsolutePathBuf`. These types must be/contain strictly absolute paths. - `CanonicalPath` and `CanonicalPathBuf`. These types must be/contain canonical paths. Operations are prohibited as necessary to ensure that the invariants of each type are upheld (needs double-checking). Only paths that are absolute (or canonical) can be easily used as / converted to `std::path::Path`s. This is to help force us to account for the emulated current working directory instead of accidentally using the current directory of the Nushell process (i.e., `std::env::current_dir`). Related to #12975 and #12976. Note that this PR uses several declarative macros, as the file / this PR would otherwise be 5000 lines long. # User-Facing Changes No major changes yet, just adds types to `nu-path` to be used in the future. # After Submitting Actually use the new path types in all our crates where it makes sense, removing usages of `std::path` types. --- crates/nu-path/src/form.rs | 161 ++ crates/nu-path/src/lib.rs | 3 + crates/nu-path/src/path.rs | 3095 ++++++++++++++++++++++++++++++++++++ typos.toml | 1 + 4 files changed, 3260 insertions(+) create mode 100644 crates/nu-path/src/form.rs create mode 100644 crates/nu-path/src/path.rs diff --git a/crates/nu-path/src/form.rs b/crates/nu-path/src/form.rs new file mode 100644 index 0000000000..4266905a20 --- /dev/null +++ b/crates/nu-path/src/form.rs @@ -0,0 +1,161 @@ +use std::ffi::OsStr; + +mod private { + use std::ffi::OsStr; + + // This trait should not be extended by external crates in order to uphold safety guarantees. + // As such, this trait is put inside a private module to prevent external impls. + // This ensures that all possible [`PathForm`]s can only be defined here and will: + // - be zero sized (enforced anyways by the `repr(transparent)` on `Path`) + // - have a no-op [`Drop`] implementation + pub trait Sealed: 'static { + fn invariants_satisfied + ?Sized>(path: &P) -> bool; + } +} + +/// A marker trait for the different kinds of path forms. +/// Each form has its own invariants that are guaranteed be upheld. +/// The list of path forms are: +/// - [`Any`]: a path with no invariants. It may be a relative or an absolute path. +/// - [`Relative`]: a strictly relative path. +/// - [`Absolute`]: a strictly absolute path. +/// - [`Canonical`]: a path that must be in canonicalized form. +pub trait PathForm: private::Sealed {} +impl PathForm for Any {} +impl PathForm for Relative {} +impl PathForm for Absolute {} +impl PathForm for Canonical {} + +/// A path whose form is unknown. It could be a relative, absolute, or canonical path. +/// +/// The path is not guaranteed to be normalized. It may contain unresolved symlinks, +/// trailing slashes, dot components (`..` or `.`), and repeated path separators. +pub struct Any; + +impl private::Sealed for Any { + fn invariants_satisfied + ?Sized>(_: &P) -> bool { + true + } +} + +/// A strictly relative path. +/// +/// The path is not guaranteed to be normalized. It may contain unresolved symlinks, +/// trailing slashes, dot components (`..` or `.`), and repeated path separators. +pub struct Relative; + +impl private::Sealed for Relative { + fn invariants_satisfied + ?Sized>(path: &P) -> bool { + std::path::Path::new(path).is_relative() + } +} + +/// An absolute path. +/// +/// The path is not guaranteed to be normalized. It may contain unresolved symlinks, +/// trailing slashes, dot components (`..` or `.`), and repeated path separators. +pub struct Absolute; + +impl private::Sealed for Absolute { + fn invariants_satisfied + ?Sized>(path: &P) -> bool { + std::path::Path::new(path).is_absolute() + } +} + +// A canonical path. +// +// An absolute path with all intermediate components normalized and symbolic links resolved. +pub struct Canonical; + +impl private::Sealed for Canonical { + fn invariants_satisfied + ?Sized>(_: &P) -> bool { + true + } +} + +/// A marker trait for [`PathForm`]s that may be relative paths. +/// This includes only the [`Any`] and [`Relative`] path forms. +/// +/// [`push`](crate::PathBuf::push) and [`join`](crate::Path::join) +/// operations only support [`MaybeRelative`] path forms as input. +pub trait MaybeRelative: PathForm {} +impl MaybeRelative for Any {} +impl MaybeRelative for Relative {} + +/// A marker trait for [`PathForm`]s that may be absolute paths. +/// This includes the [`Any`], [`Absolute`], and [`Canonical`] path forms. +pub trait MaybeAbsolute: PathForm {} +impl MaybeAbsolute for Any {} +impl MaybeAbsolute for Absolute {} +impl MaybeAbsolute for Canonical {} + +/// A marker trait for [`PathForm`]s that are absolute paths. +/// This includes only the [`Absolute`] and [`Canonical`] path forms. +/// +/// Only [`PathForm`]s that implement this trait can be easily converted to [`std::path::Path`] +/// or [`std::path::PathBuf`]. This is to encourage/force other Nushell crates to account for +/// the emulated current working directory, instead of using the [`std::env::current_dir`]. +pub trait IsAbsolute: PathForm {} +impl IsAbsolute for Absolute {} +impl IsAbsolute for Canonical {} + +/// A marker trait that signifies one [`PathForm`] can be used as or trivially converted to +/// another [`PathForm`]. +/// +/// The list of possible conversions are: +/// - [`Relative`], [`Absolute`], or [`Canonical`] into [`Any`]. +/// - [`Canonical`] into [`Absolute`]. +/// - Any form into itself. +pub trait PathCast: PathForm {} +impl PathCast
for Form {} +impl PathCast for Relative {} +impl PathCast for Absolute {} +impl PathCast for Canonical {} +impl PathCast for Canonical {} + +/// A trait used to specify the output [`PathForm`] of a path join operation. +/// +/// The output path forms based on the left hand side path form are as follows: +/// +/// | Left hand side | Output form | +/// | --------------:|:------------ | +/// | [`Any`] | [`Any`] | +/// | [`Relative`] | [`Any`] | +/// | [`Absolute`] | [`Absolute`] | +/// | [`Canonical`] | [`Absolute`] | +pub trait PathJoin: PathForm { + type Output: PathForm; +} +impl PathJoin for Any { + type Output = Self; +} +impl PathJoin for Relative { + type Output = Any; +} +impl PathJoin for Absolute { + type Output = Self; +} +impl PathJoin for Canonical { + type Output = Absolute; +} + +/// A marker trait for [`PathForm`]s that support setting the file name or extension. +/// +/// This includes the [`Any`], [`Relative`], and [`Absolute`] path forms. +/// [`Canonical`] paths do not support this, since appending file names and extensions that contain +/// path separators can cause the path to no longer be canonical. +pub trait PathSet: PathForm {} +impl PathSet for Any {} +impl PathSet for Relative {} +impl PathSet for Absolute {} + +/// A marker trait for [`PathForm`]s that support pushing [`MaybeRelative`] paths. +/// +/// This includes only [`Any`] and [`Absolute`] path forms. +/// Pushing onto a [`Relative`] path could cause it to become [`Absolute`], +/// which is why they do not support pushing. +/// In the future, a `push_rel` and/or a `try_push` method could be added as an alternative. +/// Similarly, [`Canonical`] paths may become uncanonical if a non-canonical path is pushed onto it. +pub trait PathPush: PathSet {} +impl PathPush for Any {} +impl PathPush for Absolute {} diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index 13640acd2f..8553495439 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -2,12 +2,15 @@ mod assert_path_eq; mod components; pub mod dots; pub mod expansions; +pub mod form; mod helpers; +mod path; mod tilde; mod trailing_slash; pub use components::components; pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs}; pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir}; +pub use path::*; pub use tilde::expand_tilde; pub use trailing_slash::{has_trailing_slash, strip_trailing_slash}; diff --git a/crates/nu-path/src/path.rs b/crates/nu-path/src/path.rs new file mode 100644 index 0000000000..916fffdf72 --- /dev/null +++ b/crates/nu-path/src/path.rs @@ -0,0 +1,3095 @@ +use crate::form::{ + Absolute, Any, Canonical, IsAbsolute, MaybeRelative, PathCast, PathForm, PathJoin, PathPush, + PathSet, Relative, +}; +use std::{ + borrow::{Borrow, Cow}, + cmp::Ordering, + collections::TryReserveError, + convert::Infallible, + ffi::{OsStr, OsString}, + fmt, fs, + hash::{Hash, Hasher}, + io, + iter::FusedIterator, + marker::PhantomData, + ops::{Deref, DerefMut}, + path::StripPrefixError, + rc::Rc, + str::FromStr, + sync::Arc, +}; + +/// A wrapper around [`std::path::Path`] with extra invariants determined by its `Form`. +/// +/// The possible path forms are [`Any`], [`Relative`], [`Absolute`], or [`Canonical`]. +/// To learn more, view the documentation on [`PathForm`] or any of the individual forms. +/// +/// There are also several type aliases available, corresponding to each [`PathForm`]: +/// - [`RelativePath`] (same as [`Path`]) +/// - [`AbsolutePath`] (same as [`Path`]) +/// - [`CanonicalPath`] (same as [`Path`]) +/// +/// If the `Form` is not specified, then it defaults to [`Any`], so [`Path`] and [`Path`] +/// are one in the same. +/// +/// # Converting to [`std::path`] types +/// +/// [`Path`]s with form [`Any`] cannot be easily referenced as a [`std::path::Path`] by design. +/// Other Nushell crates need to account for the emulated current working directory +/// before passing a path to functions in [`std`] or other third party crates. +/// You can [`join`](Path::join) a [`Path`] onto an [`AbsolutePath`] or a [`CanonicalPath`]. +/// This will return an [`AbsolutePathBuf`] which can be easily referenced as a [`std::path::Path`]. +/// If you really mean it, you can instead use [`as_relative_std_path`](Path::as_relative_std_path) +/// to get the underlying [`std::path::Path`] from a [`Path`]. +/// But this may cause third-party code to use [`std::env::current_dir`] to resolve +/// the path which is almost always incorrect behavior. Extra care is needed to ensure that this +/// is not the case after using [`as_relative_std_path`](Path::as_relative_std_path). +#[repr(transparent)] +pub struct Path { + _form: PhantomData, + inner: std::path::Path, +} + +/// A path that is strictly relative. +/// +/// I.e., this path is guaranteed to never be absolute. +/// +/// [`RelativePath`]s cannot be easily converted into a [`std::path::Path`] by design. +/// Other Nushell crates need to account for the emulated current working directory +/// before passing a path to functions in [`std`] or other third party crates. +/// You can [`join`](Path::join) a [`RelativePath`] onto an [`AbsolutePath`] or a [`CanonicalPath`]. +/// This will return an [`AbsolutePathBuf`] which can be referenced as a [`std::path::Path`]. +/// If you really mean it, you can use [`as_relative_std_path`](RelativePath::as_relative_std_path) +/// to get the underlying [`std::path::Path`] from a [`RelativePath`]. +/// But this may cause third-party code to use [`std::env::current_dir`] to resolve +/// the path which is almost always incorrect behavior. Extra care is needed to ensure that this +/// is not the case after using [`as_relative_std_path`](RelativePath::as_relative_std_path). +/// +/// # Examples +/// +/// [`RelativePath`]s can be created by using [`try_relative`](Path::try_relative) +/// on a [`Path`], by using [`try_new`](Path::try_new), or by using +/// [`strip_prefix`](Path::strip_prefix) on a [`Path`] of any form. +/// +/// ``` +/// use nu_path::{Path, RelativePath}; +/// +/// let path1 = Path::new("foo.txt"); +/// let path1 = path1.try_relative().unwrap(); +/// +/// let path2 = RelativePath::try_new("foo.txt").unwrap(); +/// +/// let path3 = Path::new("/prefix/foo.txt").strip_prefix("/prefix").unwrap(); +/// +/// assert_eq!(path1, path2); +/// assert_eq!(path2, path3); +/// ``` +/// +/// You can also use `RelativePath::try_from` or `try_into`. +/// This supports attempted conversions from [`Path`] as well as types in [`std::path`]. +/// +/// ``` +/// use nu_path::{Path, RelativePath}; +/// +/// let path1 = Path::new("foo.txt"); +/// let path1: &RelativePath = path1.try_into().unwrap(); +/// +/// let path2 = std::path::Path::new("foo.txt"); +/// let path2: &RelativePath = path2.try_into().unwrap(); +/// +/// assert_eq!(path1, path2) +/// ``` +pub type RelativePath = Path; + +/// A path that is strictly absolute. +/// +/// I.e., this path is guaranteed to never be relative. +/// +/// # Examples +/// +/// [`AbsolutePath`]s can be created by using [`try_absolute`](Path::try_absolute) on a [`Path`] +/// or by using [`try_new`](AbsolutePath::try_new). +/// +#[cfg_attr(not(windows), doc = "```")] +#[cfg_attr(windows, doc = "```no_run")] +/// use nu_path::{AbsolutePath, Path}; +/// +/// let path1 = Path::new("/foo").try_absolute().unwrap(); +/// let path2 = AbsolutePath::try_new("/foo").unwrap(); +/// +/// assert_eq!(path1, path2); +/// ``` +/// +/// You can also use `AbsolutePath::try_from` or `try_into`. +/// This supports attempted conversions from [`Path`] as well as types in [`std::path`]. +/// +#[cfg_attr(not(windows), doc = "```")] +#[cfg_attr(windows, doc = "```no_run")] +/// use nu_path::{AbsolutePath, Path}; +/// +/// let path1 = Path::new("/foo"); +/// let path1: &AbsolutePath = path1.try_into().unwrap(); +/// +/// let path2 = std::path::Path::new("/foo"); +/// let path2: &AbsolutePath = path2.try_into().unwrap(); +/// +/// assert_eq!(path1, path2) +/// ``` +pub type AbsolutePath = Path; + +/// An absolute, canonical path. +/// +/// # Examples +/// +/// [`CanonicalPath`]s can only be created by using [`canonicalize`](Path::canonicalize) on +/// an [`AbsolutePath`]. References to [`CanonicalPath`]s can be converted to +/// [`AbsolutePath`] references using `as_ref`, [`cast`](Path::cast), +/// or [`as_absolute`](CanonicalPath::as_absolute). +/// +/// ```no_run +/// use nu_path::AbsolutePath; +/// +/// let path = AbsolutePath::try_new("/foo").unwrap(); +/// +/// let canonical = path.canonicalize().expect("canonicalization failed"); +/// +/// assert_eq!(path, canonical.as_absolute()); +/// ``` +pub type CanonicalPath = Path; + +impl Path { + /// Create a new path of any form without validating invariants. + #[inline] + fn new_unchecked + ?Sized>(path: &P) -> &Self { + debug_assert!(Form::invariants_satisfied(path)); + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let path = std::path::Path::new(path.as_ref()); + let ptr = std::ptr::from_ref(path) as *const Self; + unsafe { &*ptr } + } + + /// Attempt to create a new [`Path`] from a reference of another type. + /// + /// This is a convenience method instead of having to use `try_into` with a type annotation. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{AbsolutePath, RelativePath}; + /// + /// assert!(AbsolutePath::try_new("foo.txt").is_err()); + /// assert!(RelativePath::try_new("foo.txt").is_ok()); + /// ``` + #[inline] + pub fn try_new<'a, T>(path: &'a T) -> Result<&'a Self, <&'a T as TryInto<&'a Self>>::Error> + where + T: ?Sized, + &'a T: TryInto<&'a Self>, + { + path.try_into() + } + + /// Returns the underlying [`OsStr`] slice. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let os_str = Path::new("foo.txt").as_os_str(); + /// assert_eq!(os_str, std::ffi::OsStr::new("foo.txt")); + /// ``` + #[must_use] + #[inline] + pub fn as_os_str(&self) -> &OsStr { + self.inner.as_os_str() + } + + /// Returns a [`str`] slice if the [`Path`] is valid unicode. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_str(), Some("foo.txt")); + /// ``` + #[inline] + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() + } + + /// Converts a [`Path`] to a `Cow`. + /// + /// Any non-Unicode sequences are replaced with `U+FFFD REPLACEMENT CHARACTER`. + /// + /// # Examples + /// + /// Calling `to_string_lossy` on a [`Path`] with valid unicode: + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_string_lossy(), "foo.txt"); + /// ``` + /// + /// Had `path` contained invalid unicode, the `to_string_lossy` call might have returned + /// `"fo�.txt"`. + #[inline] + pub fn to_string_lossy(&self) -> Cow<'_, str> { + self.inner.to_string_lossy() + } + + /// Converts a [`Path`] to an owned [`PathBuf`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let path_buf = Path::new("foo.txt").to_path_buf(); + /// assert_eq!(path_buf, PathBuf::from("foo.txt")); + /// ``` + #[inline] + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::new_unchecked(self.inner.to_path_buf()) + } + + /// Returns the [`Path`] without its final component, if there is one. + /// + /// This means it returns `Some("")` for relative paths with one component. + /// + /// Returns [`None`] if the path terminates in a root or prefix, or if it's + /// the empty string. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("/foo/bar"); + /// let parent = path.parent().unwrap(); + /// assert_eq!(parent, Path::new("/foo")); + /// + /// let grand_parent = parent.parent().unwrap(); + /// assert_eq!(grand_parent, Path::new("/")); + /// assert_eq!(grand_parent.parent(), None); + /// + /// let relative_path = Path::new("foo/bar"); + /// let parent = relative_path.parent(); + /// assert_eq!(parent, Some(Path::new("foo"))); + /// let grand_parent = parent.and_then(Path::parent); + /// assert_eq!(grand_parent, Some(Path::new(""))); + /// let great_grand_parent = grand_parent.and_then(Path::parent); + /// assert_eq!(great_grand_parent, None); + /// ``` + #[must_use] + #[inline] + pub fn parent(&self) -> Option<&Self> { + self.inner.parent().map(Self::new_unchecked) + } + + /// Produces an iterator over a [`Path`] and its ancestors. + /// + /// The iterator will yield the [`Path`] that is returned if the [`parent`](Path::parent) method + /// is used zero or more times. That means, the iterator will yield `&self`, + /// `&self.parent().unwrap()`, `&self.parent().unwrap().parent().unwrap()` and so on. + /// If the [`parent`](Path::parent) method returns [`None`], the iterator will do likewise. + /// The iterator will always yield at least one value, namely `&self`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let mut ancestors = Path::new("/foo/bar").ancestors(); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo/bar"))); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo"))); + /// assert_eq!(ancestors.next(), Some(Path::new("/"))); + /// assert_eq!(ancestors.next(), None); + /// + /// let mut ancestors = Path::new("../foo/bar").ancestors(); + /// assert_eq!(ancestors.next(), Some(Path::new("../foo/bar"))); + /// assert_eq!(ancestors.next(), Some(Path::new("../foo"))); + /// assert_eq!(ancestors.next(), Some(Path::new(".."))); + /// assert_eq!(ancestors.next(), Some(Path::new(""))); + /// assert_eq!(ancestors.next(), None); + /// ``` + #[inline] + pub fn ancestors(&self) -> Ancestors<'_, Form> { + Ancestors { + _form: PhantomData, + inner: self.inner.ancestors(), + } + } + + /// Returns the final component of a [`Path`], if there is one. + /// + /// If the path is a normal file, this is the file name. If it's the path of a directory, this + /// is the directory name. + /// + /// Returns [`None`] if the path terminates in `..`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// use std::ffi::OsStr; + /// + /// assert_eq!(Some(OsStr::new("bin")), Path::new("/usr/bin/").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("tmp/foo.txt").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name()); + /// assert_eq!(None, Path::new("foo.txt/..").file_name()); + /// assert_eq!(None, Path::new("/").file_name()); + /// ``` + #[must_use] + #[inline] + pub fn file_name(&self) -> Option<&OsStr> { + self.inner.file_name() + } + + /// Returns a relative path that, when joined onto `base`, yields `self`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let path = Path::new("/test/haha/foo.txt"); + /// + /// assert_eq!(path.strip_prefix("/").unwrap(), Path::new("test/haha/foo.txt")); + /// assert_eq!(path.strip_prefix("/test").unwrap(), Path::new("haha/foo.txt")); + /// assert_eq!(path.strip_prefix("/test/").unwrap(), Path::new("haha/foo.txt")); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt").unwrap(), Path::new("")); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/").unwrap(), Path::new("")); + /// + /// assert!(path.strip_prefix("test").is_err()); + /// assert!(path.strip_prefix("/haha").is_err()); + /// + /// let prefix = PathBuf::from("/test/"); + /// assert_eq!(path.strip_prefix(prefix).unwrap(), Path::new("haha/foo.txt")); + /// ``` + #[inline] + pub fn strip_prefix(&self, base: impl AsRef) -> Result<&RelativePath, StripPrefixError> { + self.inner + .strip_prefix(&base.as_ref().inner) + .map(RelativePath::new_unchecked) + } + + /// Determines whether `base` is a prefix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.starts_with("/etc")); + /// assert!(path.starts_with("/etc/")); + /// assert!(path.starts_with("/etc/passwd")); + /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay + /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay + /// + /// assert!(!path.starts_with("/e")); + /// assert!(!path.starts_with("/etc/passwd.txt")); + /// + /// assert!(!Path::new("/etc/foo.rs").starts_with("/etc/foo")); + /// ``` + #[must_use] + #[inline] + pub fn starts_with(&self, base: impl AsRef) -> bool { + self.inner.starts_with(&base.as_ref().inner) + } + + /// Determines whether `child` is a suffix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("/etc/resolv.conf"); + /// + /// assert!(path.ends_with("resolv.conf")); + /// assert!(path.ends_with("etc/resolv.conf")); + /// assert!(path.ends_with("/etc/resolv.conf")); + /// + /// assert!(!path.ends_with("/resolv.conf")); + /// assert!(!path.ends_with("conf")); // use .extension() instead + /// ``` + #[must_use] + #[inline] + pub fn ends_with(&self, child: impl AsRef) -> bool { + self.inner.ends_with(&child.as_ref().inner) + } + + /// Extracts the stem (non-extension) portion of [`self.file_name`](Path::file_name). + /// + /// The stem is: + /// + /// * [`None`], if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert_eq!("foo", Path::new("foo.rs").file_stem().unwrap()); + /// assert_eq!("foo.tar", Path::new("foo.tar.gz").file_stem().unwrap()); + /// ``` + #[must_use] + #[inline] + pub fn file_stem(&self) -> Option<&OsStr> { + self.inner.file_stem() + } + + /// Extracts the extension (without the leading dot) of [`self.file_name`](Path::file_name), + /// if possible. + /// + /// The extension is: + /// + /// * [`None`], if there is no file name; + /// * [`None`], if there is no embedded `.`; + /// * [`None`], if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert_eq!("rs", Path::new("foo.rs").extension().unwrap()); + /// assert_eq!("gz", Path::new("foo.tar.gz").extension().unwrap()); + /// ``` + #[must_use] + #[inline] + pub fn extension(&self) -> Option<&OsStr> { + self.inner.extension() + } + + /// Produces an iterator over the [`Component`](std::path::Component)s of the path. + /// + /// When parsing the path, there is a small amount of normalization: + /// + /// * Repeated separators are ignored, so `a/b` and `a//b` both have + /// `a` and `b` as components. + /// + /// * Occurrences of `.` are normalized away, except if they are at the + /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and + /// `a/b` all have `a` and `b` as components, but `./a/b` starts with + /// an additional [`CurDir`](std::path::Component) component. + /// + /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. + /// + /// Note that no other normalization takes place; in particular, `a/c` + /// and `a/b/../c` are distinct, to account for the possibility that `b` + /// is a symbolic link (so its parent isn't `a`). + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// use std::path::Component; + /// use std::ffi::OsStr; + /// + /// let mut components = Path::new("/tmp/foo.txt").components(); + /// + /// assert_eq!(components.next(), Some(Component::RootDir)); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); + /// assert_eq!(components.next(), None) + /// ``` + #[inline] + pub fn components(&self) -> std::path::Components<'_> { + self.inner.components() + } + + /// Produces an iterator over the path's components viewed as [`OsStr`] slices. + /// + /// For more information about the particulars of how the path is separated into components, + /// see [`components`](Path::components). + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// use std::ffi::OsStr; + /// + /// let mut it = Path::new("/tmp/foo.txt").iter(); + /// assert_eq!(it.next(), Some(OsStr::new(&std::path::MAIN_SEPARATOR.to_string()))); + /// assert_eq!(it.next(), Some(OsStr::new("tmp"))); + /// assert_eq!(it.next(), Some(OsStr::new("foo.txt"))); + /// assert_eq!(it.next(), None) + /// ``` + #[inline] + pub fn iter(&self) -> std::path::Iter<'_> { + self.inner.iter() + } + + /// Returns an object that implements [`Display`](fmt::Display) for safely printing paths + /// that may contain non-Unicode data. This may perform lossy conversion, + /// depending on the platform. If you would like an implementation which escapes the path + /// please use [`Debug`](fmt::Debug) instead. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); + /// ``` + #[inline] + pub fn display(&self) -> std::path::Display<'_> { + self.inner.display() + } + + /// Converts a [`Box`](Box) into a [`PathBuf`] without copying or allocating. + #[inline] + pub fn into_path_buf(self: Box) -> PathBuf { + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let ptr = Box::into_raw(self) as *mut std::path::Path; + let boxed = unsafe { Box::from_raw(ptr) }; + PathBuf::new_unchecked(boxed.into_path_buf()) + } + + /// Returns a reference to the same [`Path`] in a different form. + /// + /// [`PathForm`]s can be converted to one another based on [`PathCast`] implementations. + /// Namely, the following form conversions are possible: + /// - [`Relative`], [`Absolute`], or [`Canonical`] into [`Any`]. + /// - [`Canonical`] into [`Absolute`]. + /// - Any form into itself. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, RelativePath}; + /// + /// let relative = RelativePath::try_new("test.txt").unwrap(); + /// let p: &Path = relative.cast(); + /// assert_eq!(p, relative); + /// ``` + #[inline] + pub fn cast(&self) -> &Path + where + To: PathForm, + Form: PathCast, + { + Path::new_unchecked(self) + } + + /// Returns a reference to a path with its form as [`Any`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, RelativePath}; + /// + /// let p = RelativePath::try_new("test.txt").unwrap(); + /// assert_eq!(Path::new("test.txt"), p.as_any()); + /// ``` + #[inline] + pub fn as_any(&self) -> &Path { + Path::new_unchecked(self) + } +} + +impl Path { + /// Create a new [`Path`] by wrapping a string slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// You can create [`Path`]s from [`String`]s, or even other [`Path`]s: + /// + /// ``` + /// use nu_path::Path; + /// + /// let string = String::from("foo.txt"); + /// let from_string = Path::new(&string); + /// let from_path = Path::new(&from_string); + /// assert_eq!(from_string, from_path); + /// ``` + #[inline] + pub fn new + ?Sized>(path: &P) -> &Self { + Self::new_unchecked(path) + } + + /// Returns a mutable reference to the underlying [`OsStr`] slice. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let mut path = PathBuf::from("Foo.TXT"); + /// + /// assert_ne!(path, Path::new("foo.txt")); + /// + /// path.as_mut_os_str().make_ascii_lowercase(); + /// assert_eq!(path, Path::new("foo.txt")); + /// ``` + #[must_use] + #[inline] + pub fn as_mut_os_str(&mut self) -> &mut OsStr { + self.inner.as_mut_os_str() + } + + /// Returns `true` if the [`Path`] is absolute, i.e., if it is independent of + /// the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, + /// so [`is_absolute`](Path::is_absolute) and [`has_root`](Path::has_root) are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the root: + /// `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + #[must_use] + #[inline] + pub fn is_absolute(&self) -> bool { + self.inner.is_absolute() + } + + // Returns `true` if the [`Path`] is relative, i.e., not absolute. + /// + /// See [`is_absolute`](Path::is_absolute)'s documentation for more details. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + #[must_use] + #[inline] + pub fn is_relative(&self) -> bool { + self.inner.is_relative() + } + + /// Returns an `Ok` [`AbsolutePath`] if the [`Path`] is absolute. + /// Otherwise, returns an `Err` [`RelativePath`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert!(Path::new("test.txt").try_absolute().is_err()); + /// ``` + #[inline] + pub fn try_absolute(&self) -> Result<&AbsolutePath, &RelativePath> { + if self.is_absolute() { + Ok(AbsolutePath::new_unchecked(&self.inner)) + } else { + Err(RelativePath::new_unchecked(&self.inner)) + } + } + + /// Returns an `Ok` [`RelativePath`] if the [`Path`] is relative. + /// Otherwise, returns an `Err` [`AbsolutePath`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert!(Path::new("test.txt").try_relative().is_ok()); + /// ``` + #[inline] + pub fn try_relative(&self) -> Result<&RelativePath, &AbsolutePath> { + if self.is_relative() { + Ok(RelativePath::new_unchecked(&self.inner)) + } else { + Err(AbsolutePath::new_unchecked(&self.inner)) + } + } +} + +impl Path { + /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// See [`PathBuf::push`] for more details on what it means to adjoin a path. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// assert_eq!(Path::new("/etc").join("/bin/sh"), PathBuf::from("/bin/sh")); + /// ``` + #[must_use] + #[inline] + pub fn join(&self, path: impl AsRef) -> PathBuf { + PathBuf::new_unchecked(self.inner.join(&path.as_ref().inner)) + } +} + +impl Path { + /// Creates an owned [`PathBuf`] like `self` but with the given file name. + /// + /// See [`PathBuf::set_file_name`] for more details. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let path = Path::new("/tmp/foo.png"); + /// assert_eq!(path.with_file_name("bar"), PathBuf::from("/tmp/bar")); + /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); + /// + /// let path = Path::new("/tmp"); + /// assert_eq!(path.with_file_name("var"), PathBuf::from("/var")); + /// ``` + #[inline] + pub fn with_file_name(&self, file_name: impl AsRef) -> PathBuf { + PathBuf::new_unchecked(self.inner.with_file_name(file_name)) + } + + /// Creates an owned [`PathBuf`] like `self` but with the given extension. + /// + /// See [`PathBuf::set_extension`] for more details. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); + /// + /// let path = Path::new("foo.tar.gz"); + /// assert_eq!(path.with_extension(""), PathBuf::from("foo.tar")); + /// assert_eq!(path.with_extension("xz"), PathBuf::from("foo.tar.xz")); + /// assert_eq!(path.with_extension("").with_extension("txt"), PathBuf::from("foo.txt")); + /// ``` + #[inline] + pub fn with_extension(&self, extension: impl AsRef) -> PathBuf { + PathBuf::new_unchecked(self.inner.with_extension(extension)) + } +} + +impl Path { + /// Returns the, potentially relative, underlying [`std::path::Path`]. + /// + /// # Note + /// + /// Caution should be taken when using this function. Nushell keeps track of an emulated current + /// working directory, and using the [`std::path::Path`] returned from this method will likely + /// use [`std::env::current_dir`] to resolve the path instead of using the emulated current + /// working directory. + /// + /// Instead, you should probably join this path onto the emulated current working directory. + /// Any [`AbsolutePath`] or [`CanonicalPath`] will also suffice. + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// let p = Path::new("test.txt"); + /// assert_eq!(std::path::Path::new("test.txt"), p.as_relative_std_path()); + /// ``` + #[inline] + pub fn as_relative_std_path(&self) -> &std::path::Path { + &self.inner + } + + // Returns `true` if the [`Path`] has a root. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g., `\windows` + /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g., `\\server\share` + /// + /// # Examples + /// + /// ``` + /// use nu_path::Path; + /// + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + #[must_use] + #[inline] + pub fn has_root(&self) -> bool { + self.inner.has_root() + } +} + +impl Path { + /// Returns the underlying [`std::path::Path`]. + /// + /// # Examples + /// + #[cfg_attr(not(windows), doc = "```")] + #[cfg_attr(windows, doc = "```no_run")] + /// use nu_path::AbsolutePath; + /// + /// let p = AbsolutePath::try_new("/test").unwrap(); + /// assert_eq!(std::path::Path::new("/test"), p.as_std_path()); + /// ``` + #[inline] + pub fn as_std_path(&self) -> &std::path::Path { + &self.inner + } + + /// Converts a [`Path`] to an owned [`std::path::PathBuf`]. + /// + /// # Examples + /// + #[cfg_attr(not(windows), doc = "```")] + #[cfg_attr(windows, doc = "```no_run")] + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/foo").unwrap(); + /// assert_eq!(path.to_std_path_buf(), std::path::PathBuf::from("/foo")); + /// ``` + #[inline] + pub fn to_std_path_buf(&self) -> std::path::PathBuf { + self.inner.to_path_buf() + } + + /// Queries the file system to get information about a file, directory, etc. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// + /// This is an alias to [`std::fs::metadata`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/Minas/tirith").unwrap(); + /// let metadata = path.metadata().expect("metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// ``` + #[inline] + pub fn metadata(&self) -> io::Result { + self.inner.metadata() + } + + /// Returns an iterator over the entries within a directory. + /// + /// The iterator will yield instances of [io::Result]<[fs::DirEntry]>. + /// New errors may be encountered after an iterator is initially constructed. + /// + /// This is an alias to [`std::fs::read_dir`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/laputa").unwrap(); + /// for entry in path.read_dir().expect("read_dir call failed") { + /// if let Ok(entry) = entry { + /// println!("{:?}", entry.path()); + /// } + /// } + /// ``` + #[inline] + pub fn read_dir(&self) -> io::Result { + self.inner.read_dir() + } + + /// Returns `true` if the path points at an existing entity. + /// + /// Warning: this method may be error-prone, consider using [`try_exists`](Path::try_exists) + /// instead! It also has a risk of introducing time-of-check to time-of-use (TOCTOU) bugs. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// + /// If you cannot access the metadata of the file, e.g. because of a permission error + /// or broken symbolic links, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/does_not_exist").unwrap(); + /// assert!(!path.exists()); + /// ``` + #[must_use] + #[inline] + pub fn exists(&self) -> bool { + self.inner.exists() + } + + /// Returns `true` if the path exists on disk and is pointing at a regular file. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// + /// If you cannot access the metadata of the file, e.g. because of a permission error + /// or broken symbolic links, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/is_a_directory/").unwrap(); + /// assert_eq!(path.is_file(), false); + /// + /// let path = AbsolutePath::try_new("/a_file.txt").unwrap(); + /// assert_eq!(path.is_file(), true); + /// ``` + /// + /// # See Also + /// + /// When the goal is simply to read from (or write to) the source, the most reliable way + /// to test the source can be read (or written to) is to open it. Only using `is_file` can + /// break workflows like `diff <( prog_a )` on a Unix-like system for example. + /// See [`std::fs::File::open`] or [`std::fs::OpenOptions::open`] for more information. + #[must_use] + #[inline] + pub fn is_file(&self) -> bool { + self.inner.is_file() + } + + /// Returns `true` if the path exists on disk and is pointing at a directory. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// + /// If you cannot access the metadata of the file, e.g. because of a permission error + /// or broken symbolic links, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/is_a_directory/").unwrap(); + /// assert_eq!(path.is_dir(), true); + /// + /// let path = AbsolutePath::try_new("/a_file.txt").unwrap(); + /// assert_eq!(path.is_dir(), false); + /// ``` + #[must_use] + #[inline] + pub fn is_dir(&self) -> bool { + self.inner.is_dir() + } +} + +impl AbsolutePath { + /// Returns the canonical, absolute form of the path with all intermediate components + /// normalized and symbolic links resolved. + /// + /// On Windows, this will also simplify to a winuser path. + /// + /// This is an alias to [`std::fs::canonicalize`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::{AbsolutePath, PathBuf}; + /// + /// let path = AbsolutePath::try_new("/foo/test/../test/bar.rs").unwrap(); + /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// ``` + #[cfg(not(windows))] + #[inline] + pub fn canonicalize(&self) -> io::Result { + self.inner + .canonicalize() + .map(CanonicalPathBuf::new_unchecked) + } + + /// Returns the canonical, absolute form of the path with all intermediate components + /// normalized and symbolic links resolved. + /// + /// On Windows, this will also simplify to a winuser path. + /// + /// This is an alias to [`std::fs::canonicalize`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::{AbsolutePath, PathBuf}; + /// + /// let path = AbsolutePath::try_new("/foo/test/../test/bar.rs").unwrap(); + /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// ``` + #[cfg(windows)] + pub fn canonicalize(&self) -> io::Result { + use omnipath::WinPathExt; + + let path = self.inner.canonicalize()?.to_winuser_path()?; + Ok(CanonicalPathBuf::new_unchecked(path)) + } + + /// Reads a symbolic link, returning the file that the link points to. + /// + /// This is an alias to [`std::fs::read_link`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/laputa/sky_castle.rs").unwrap(); + /// let path_link = path.read_link().expect("read_link call failed"); + /// ``` + #[inline] + pub fn read_link(&self) -> io::Result { + self.inner.read_link().map(PathBuf::new_unchecked) + } + + /// Returns `Ok(true)` if the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// In case of broken symbolic links this will return `Ok(false)`. + /// + /// [`Path::exists`] only checks whether or not a path was both found and readable. + /// By contrast, [`try_exists`](Path::try_exists) will return `Ok(true)` or `Ok(false)`, + /// respectively, if the path was _verified_ to exist or not exist. + /// If its existence can neither be confirmed nor denied, it will propagate an `Err` instead. + /// This can be the case if e.g. listing permission is denied on one of the parent directories. + /// + /// Note that while this avoids some pitfalls of the [`exists`](Path::exists) method, + /// it still can not prevent time-of-check to time-of-use (TOCTOU) bugs. + /// You should only use it in scenarios where those bugs are not an issue. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/does_not_exist").unwrap(); + /// assert!(!path.try_exists().unwrap()); + /// + /// let path = AbsolutePath::try_new("/root/secret_file.txt").unwrap(); + /// assert!(path.try_exists().is_err()); + /// ``` + #[inline] + pub fn try_exists(&self) -> io::Result { + self.inner.try_exists() + } + + /// Returns `true` if the path exists on disk and is pointing at a symbolic link. + /// + /// This function will not traverse symbolic links. + /// In case of a broken symbolic link this will also return true. + /// + /// If you cannot access the directory containing the file, e.g., because of a permission error, + /// this will return false. + /// + /// # Examples + /// + #[cfg_attr(unix, doc = "```no_run")] + #[cfg_attr(not(unix), doc = "```ignore")] + /// use nu_path::AbsolutePath; + /// use std::os::unix::fs::symlink; + /// + /// let link_path = AbsolutePath::try_new("/link").unwrap(); + /// symlink("/origin_does_not_exist/", link_path).unwrap(); + /// assert_eq!(link_path.is_symlink(), true); + /// assert_eq!(link_path.exists(), false); + /// ``` + #[must_use] + #[inline] + pub fn is_symlink(&self) -> bool { + self.inner.is_symlink() + } + + /// Queries the metadata about a file without following symlinks. + /// + /// This is an alias to [`std::fs::symlink_metadata`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let path = AbsolutePath::try_new("/Minas/tirith").unwrap(); + /// let metadata = path.symlink_metadata().expect("symlink_metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// ``` + #[inline] + pub fn symlink_metadata(&self) -> io::Result { + self.inner.symlink_metadata() + } +} + +impl CanonicalPath { + /// Returns a [`CanonicalPath`] as a [`AbsolutePath`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePath; + /// + /// let absolute = AbsolutePath::try_new("/test").unwrap(); + /// let p = absolute.canonicalize().unwrap(); + /// assert_eq!(absolute, p.as_absolute()); + /// ``` + #[inline] + pub fn as_absolute(&self) -> &AbsolutePath { + self.cast() + } +} + +impl fmt::Debug for Path { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.inner, fmt) + } +} + +impl Clone for Box> { + #[inline] + fn clone(&self) -> Self { + std_box_to_box(self.inner.into()) + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + + #[inline] + fn to_owned(&self) -> Self::Owned { + self.to_path_buf() + } + + #[inline] + fn clone_into(&self, target: &mut PathBuf) { + self.inner.clone_into(&mut target.inner); + } +} + +impl<'a, Form: PathForm> IntoIterator for &'a Path { + type Item = &'a OsStr; + + type IntoIter = std::path::Iter<'a>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator over [`Path`] and its ancestors. +/// +/// This `struct` is created by the [`ancestors`](Path::ancestors) method on [`Path`]. +/// See its documentation for more. +/// +/// # Examples +/// +/// ``` +/// use nu_path::Path; +/// +/// let path = Path::new("/foo/bar"); +/// +/// for ancestor in path.ancestors() { +/// println!("{}", ancestor.display()); +/// } +/// ``` +#[derive(Clone, Copy)] +pub struct Ancestors<'a, Form: PathForm> { + _form: PhantomData, + inner: std::path::Ancestors<'a>, +} + +impl fmt::Debug for Ancestors<'_, Form> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.inner, f) + } +} + +impl<'a, Form: PathForm> Iterator for Ancestors<'a, Form> { + type Item = &'a Path; + + fn next(&mut self) -> Option { + self.inner.next().map(Path::new_unchecked) + } +} + +impl FusedIterator for Ancestors<'_, Form> {} + +/// A wrapper around [`std::path::PathBuf`] with extra invariants determined by its `Form`. +/// +/// The possible path forms are [`Any`], [`Relative`], [`Absolute`], or [`Canonical`]. +/// To learn more, view the documentation on [`PathForm`] or any of the individual forms. +/// +/// There are also several type aliases available, corresponding to each [`PathForm`]: +/// - [`RelativePathBuf`] (same as [`PathBuf`]) +/// - [`AbsolutePathBuf`] (same as [`PathBuf`]) +/// - [`CanonicalPathBuf`] (same as [`PathBuf`]) +/// +/// If the `Form` is not specified, then it defaults to [`Any`], +/// so [`PathBuf`] and [`PathBuf`] are one in the same. +/// +/// # Examples +/// +/// To create a [`PathBuf`] with [`Any`] form, you can use the same techniques as when creating +/// a [`std::path::PathBuf`]. +/// +/// ``` +/// use nu_path::PathBuf; +/// +/// let path = PathBuf::from(r"C:\windows\system32.dll"); +/// +/// let mut path1 = PathBuf::new(); +/// path1.push(r"C:\"); +/// path1.push("windows"); +/// path1.push("system32"); +/// path1.set_extension("dll"); +/// +/// let path2: PathBuf = [r"C:\", "windows", "system32.dll"].iter().collect(); +/// +/// assert_eq!(path1, path2); +/// ``` +/// +/// # Converting to [`std::path`] types +/// +/// [`PathBuf`]s with form [`Any`] cannot be easily referenced as a [`std::path::Path`] +/// or converted to a [`std::path::PathBuf`] by design. +/// Other Nushell crates need to account for the emulated current working directory +/// before passing a path to functions in [`std`] or other third party crates. +/// You can [`join`](Path::join) a [`Path`] onto an [`AbsolutePath`] or a [`CanonicalPath`]. +/// This will return an [`AbsolutePathBuf`] which can be easily referenced as a [`std::path::Path`]. +/// If you really mean it, you can instead use [`as_relative_std_path`](Path::as_relative_std_path) +/// or [`into_relative_std_path_buf`](PathBuf::into_relative_std_path_buf) +/// to get the underlying [`std::path::Path`] or [`std::path::PathBuf`] from a [`PathBuf`]. +/// But this may cause third-party code to use [`std::env::current_dir`] to resolve +/// the path which is almost always incorrect behavior. Extra care is needed to ensure that this +/// is not the case after using [`as_relative_std_path`](Path::as_relative_std_path) +/// or [`into_relative_std_path_buf`](PathBuf::into_relative_std_path_buf). +#[repr(transparent)] +pub struct PathBuf { + _form: PhantomData, + inner: std::path::PathBuf, +} + +/// A path buf that is strictly relative. +/// +/// I.e., this path buf is guaranteed to never be absolute. +/// +/// [`RelativePathBuf`]s cannot be easily referenced as a [`std::path::Path`] +/// or converted to a [`std::path::PathBuf`] by design. +/// Other Nushell crates need to account for the emulated current working directory +/// before passing a path to functions in [`std`] or other third party crates. +/// You can [`join`](Path::join) a [`RelativePath`] onto an [`AbsolutePath`] or a [`CanonicalPath`]. +/// This will return an [`AbsolutePathBuf`] which can be easily referenced as a [`std::path::Path`]. +/// If you really mean it, you can instead use +/// [`as_relative_std_path`](RelativePath::as_relative_std_path) +/// or [`into_relative_std_path_buf`](RelativePathBuf::into_relative_std_path_buf) +/// to get the underlying [`std::path::Path`] or [`std::path::PathBuf`] from a [`RelativePathBuf`]. +/// But this may cause third-party code to use [`std::env::current_dir`] to resolve +/// the path which is almost always incorrect behavior. Extra care is needed to ensure that this +/// is not the case after using [`as_relative_std_path`](RelativePath::as_relative_std_path) +/// or [`into_relative_std_path_buf`](RelativePathBuf::into_relative_std_path_buf). +/// +/// # Examples +/// +/// [`RelativePathBuf`]s can be created by using [`try_into_relative`](PathBuf::try_into_relative) +/// on a [`PathBuf`] or by using [`to_path_buf`](Path::to_path_buf) on a [`RelativePath`]. +/// +/// ``` +/// use nu_path::{PathBuf, RelativePath, RelativePathBuf}; +/// +/// let path_buf = PathBuf::from("foo.txt"); +/// let path_buf = path_buf.try_into_relative().unwrap(); +/// +/// let path = RelativePath::try_new("foo.txt").unwrap(); +/// let path_buf2 = path.to_path_buf(); +/// +/// assert_eq!(path_buf, path_buf2); +/// ``` +/// +/// You can also use `RelativePathBuf::try_from` or `try_into`. +/// This supports attempted conversions from [`Path`] as well as types in [`std::path`]. +/// +/// ``` +/// use nu_path::{Path, RelativePathBuf}; +/// +/// let path1 = RelativePathBuf::try_from("foo.txt").unwrap(); +/// +/// let path2 = Path::new("foo.txt"); +/// let path2 = RelativePathBuf::try_from(path2).unwrap(); +/// +/// let path3 = std::path::PathBuf::from("foo.txt"); +/// let path3: RelativePathBuf = path3.try_into().unwrap(); +/// +/// assert_eq!(path1, path2); +/// assert_eq!(path2, path3); +/// ``` +pub type RelativePathBuf = PathBuf; + +/// A path buf that is strictly absolute. +/// +/// I.e., this path buf is guaranteed to never be relative. +/// +/// # Examples +/// +/// [`AbsolutePathBuf`]s can be created by using [`try_into_absolute`](PathBuf::try_into_absolute) +/// on a [`PathBuf`] or by using [`to_path_buf`](Path::to_path_buf) on an [`AbsolutePath`]. +/// +#[cfg_attr(not(windows), doc = "```")] +#[cfg_attr(windows, doc = "```no_run")] +/// use nu_path::{AbsolutePath, AbsolutePathBuf, PathBuf}; +/// +/// let path_buf1 = PathBuf::from("/foo"); +/// let path_buf1 = path_buf1.try_into_absolute().unwrap(); +/// +/// let path = AbsolutePath::try_new("/foo").unwrap(); +/// let path_buf2 = path.to_path_buf(); +/// +/// assert_eq!(path_buf1, path_buf2); +/// ``` +/// +/// You can also use `AbsolutePathBuf::try_from` or `try_into`. +/// This supports attempted conversions from [`Path`] as well as types in [`std::path`]. +/// +#[cfg_attr(not(windows), doc = "```")] +#[cfg_attr(windows, doc = "```no_run")] +/// use nu_path::{AbsolutePathBuf, Path}; +/// +/// let path1 = AbsolutePathBuf::try_from("/foo").unwrap(); +/// +/// let path2 = Path::new("/foo"); +/// let path2 = AbsolutePathBuf::try_from(path2).unwrap(); +/// +/// let path3 = std::path::PathBuf::from("/foo"); +/// let path3: AbsolutePathBuf = path3.try_into().unwrap(); +/// +/// assert_eq!(path1, path2); +/// assert_eq!(path2, path3); +/// ``` +pub type AbsolutePathBuf = PathBuf; + +/// An absolute, canonical path buf. +/// +/// # Examples +/// +/// [`CanonicalPathBuf`]s can only be created by using [`canonicalize`](Path::canonicalize) on +/// an [`AbsolutePath`]. [`CanonicalPathBuf`]s can be converted back to [`AbsolutePathBuf`]s via +/// [`into_absolute`](CanonicalPathBuf::into_absolute). +/// +/// ```no_run +/// use nu_path::AbsolutePathBuf; +/// +/// let path = AbsolutePathBuf::try_from("/foo").unwrap(); +/// +/// let canonical = path.canonicalize().expect("canonicalization failed"); +/// +/// assert_eq!(path, canonical.into_absolute()); +/// ``` +pub type CanonicalPathBuf = PathBuf; + +impl PathBuf { + /// Create a new [`PathBuf`] of any form without validiting invariants. + #[inline] + pub(crate) fn new_unchecked(buf: std::path::PathBuf) -> Self { + debug_assert!(Form::invariants_satisfied(&buf)); + Self { + _form: PhantomData, + inner: buf, + } + } + + /// Coerces to a [`Path`] slice. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let p = PathBuf::from("/test"); + /// assert_eq!(Path::new("/test"), p.as_path()); + /// ``` + #[must_use] + #[inline] + pub fn as_path(&self) -> &Path { + Path::new_unchecked(&self.inner) + } + + /// Truncates `self` to [`self.parent`](Path::parent). + /// + /// Returns `false` and does nothing if [`self.parent`](Path::parent) is [`None`]. + /// Otherwise, returns `true`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/spirited/away.rs"); + /// + /// p.pop(); + /// assert_eq!(Path::new("/spirited"), p); + /// p.pop(); + /// assert_eq!(Path::new("/"), p); + /// ``` + #[inline] + pub fn pop(&mut self) -> bool { + self.inner.pop() + } + + /// Consumes the [`PathBuf`], returning its internal [`OsString`] storage. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let p = PathBuf::from("/the/head"); + /// let os_str = p.into_os_string(); + /// ``` + #[inline] + pub fn into_os_string(self) -> OsString { + self.inner.into_os_string() + } + + /// Converts this [`PathBuf`] into a [boxed](Box) [`Path`]. + #[inline] + pub fn into_boxed_path(self) -> Box> { + std_box_to_box(self.inner.into_boxed_path()) + } + + /// Returns the [`capacity`](OsString::capacity) of the underlying [`OsString`]. + #[must_use] + #[inline] + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Invokes [`reserve`](OsString::reserve) on the underlying [`OsString`]. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional) + } + + /// Invokes [`try_reserve`](OsString::try_reserve) on the underlying [`OsString`]. + #[inline] + pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.inner.try_reserve(additional) + } + + /// Invokes [`reserve_exact`](OsString::reserve_exact) on the underlying [`OsString`]. + #[inline] + pub fn reserve_exact(&mut self, additional: usize) { + self.inner.reserve_exact(additional) + } + + /// Invokes [`try_reserve_exact`](OsString::try_reserve_exact) on the underlying [`OsString`]. + #[inline] + pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.inner.try_reserve_exact(additional) + } + + /// Invokes [`shrink_to_fit`](OsString::shrink_to_fit) on the underlying [`OsString`]. + #[inline] + pub fn shrink_to_fit(&mut self) { + self.inner.shrink_to_fit() + } + + /// Invokes [`shrink_to`](OsString::shrink_to) on the underlying [`OsString`]. + #[inline] + pub fn shrink_to(&mut self, min_capacity: usize) { + self.inner.shrink_to(min_capacity) + } + + /// Consumes a [`PathBuf`], returning it with a different form. + /// + /// [`PathForm`]s can be converted to one another based on [`PathCast`] implementations. + /// Namely, the following form conversions are possible: + /// - [`Relative`], [`Absolute`], or [`Canonical`] into [`Any`]. + /// - [`Canonical`] into [`Absolute`]. + /// - Any form into itself. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{PathBuf, RelativePathBuf}; + /// + /// let p = RelativePathBuf::try_from("test.txt").unwrap(); + /// let p: PathBuf = p.cast_into(); + /// assert_eq!(PathBuf::from("test.txt"), p); + /// ``` + #[inline] + pub fn cast_into(self) -> PathBuf + where + To: PathForm, + Form: PathCast, + { + PathBuf::new_unchecked(self.inner) + } + + /// Consumes a [`PathBuf`], returning it with form [`Any`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{PathBuf, RelativePathBuf}; + /// + /// let p = RelativePathBuf::try_from("test.txt").unwrap(); + /// assert_eq!(PathBuf::from("test.txt"), p.into_any()); + /// ``` + #[inline] + pub fn into_any(self) -> PathBuf { + PathBuf::new_unchecked(self.inner) + } +} + +impl PathBuf { + /// Creates an empty [`PathBuf`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let path = PathBuf::new(); + /// ``` + #[must_use] + #[inline] + pub fn new() -> Self { + Self::new_unchecked(std::path::PathBuf::new()) + } + + /// Creates a new [`PathBuf`] with a given capacity used to create the internal [`OsString`]. + /// See [`with_capacity`](OsString::with_capacity) defined on [`OsString`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let mut path = PathBuf::with_capacity(10); + /// let capacity = path.capacity(); + /// + /// // This push is done without reallocating + /// path.push(r"C:\"); + /// + /// assert_eq!(capacity, path.capacity()); + /// ``` + #[inline] + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self::new_unchecked(std::path::PathBuf::with_capacity(capacity)) + } + + /// Returns a mutable reference to the underlying [`OsString`]. + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let mut path = PathBuf::from("/foo"); + /// + /// path.push("bar"); + /// assert_eq!(path, Path::new("/foo/bar")); + /// + /// // OsString's `push` does not add a separator. + /// path.as_mut_os_string().push("baz"); + /// assert_eq!(path, Path::new("/foo/barbaz")); + /// ``` + #[must_use] + #[inline] + pub fn as_mut_os_string(&mut self) -> &mut OsString { + self.inner.as_mut_os_string() + } + + /// Invokes [`clear`](OsString::clear) on the underlying [`OsString`]. + #[inline] + pub fn clear(&mut self) { + self.inner.clear() + } + + /// Consumes a [`PathBuf`], returning an `Ok` [`RelativePathBuf`] if the [`PathBuf`] + /// is relative. Otherwise, returns the original [`PathBuf`] as an `Err`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// assert!(PathBuf::from("test.txt").try_into_relative().is_ok()); + /// ``` + #[inline] + pub fn try_into_relative(self) -> Result { + if self.inner.is_relative() { + Ok(PathBuf::new_unchecked(self.inner)) + } else { + Err(self) + } + } + + /// Consumes a [`PathBuf`], returning an `Ok` [`AbsolutePathBuf`] if the [`PathBuf`] + /// is absolute. Otherwise, returns the original [`PathBuf`] as an `Err`. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// assert!(PathBuf::from("test.txt").try_into_absolute().is_err()); + /// ``` + #[inline] + pub fn try_into_absolute(self) -> Result { + if self.inner.is_absolute() { + Ok(PathBuf::new_unchecked(self.inner)) + } else { + Err(self) + } + } +} + +impl PathBuf { + /// Extends `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g., `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self`. + /// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`) + /// and `path` is not empty, the new path is normalized: all references + /// to `.` and `..` are removed. + /// + /// Consider using [`Path::join`] if you need a new [`PathBuf`] instead of + /// using this function on a cloned [`PathBuf`]. + /// + /// # Examples + /// + /// Pushing a relative path extends the existing path: + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let mut path = PathBuf::from("/tmp"); + /// path.push("file.bk"); + /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); + /// ``` + /// + /// Pushing an absolute path replaces the existing path: + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let mut path = PathBuf::from("/tmp"); + /// path.push("/etc"); + /// assert_eq!(path, PathBuf::from("/etc")); + /// ``` + #[inline] + pub fn push(&mut self, path: impl AsRef) { + self.inner.push(&path.as_ref().inner) + } +} + +impl PathBuf { + /// Updates [`self.file_name`](Path::file_name) to `file_name`. + /// + /// If [`self.file_name`](Path::file_name) was [`None`], + /// this is equivalent to pushing `file_name`. + /// + /// Otherwise it is equivalent to calling [`pop`](PathBuf::pop) and then pushing `file_name`. + /// The new path will be a sibling of the original path. + /// (That is, it will have the same parent.) + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let mut buf = PathBuf::from("/"); + /// assert!(buf.file_name() == None); + /// + /// buf.set_file_name("foo.txt"); + /// assert!(buf == PathBuf::from("/foo.txt")); + /// assert!(buf.file_name().is_some()); + /// + /// buf.set_file_name("bar.txt"); + /// assert!(buf == PathBuf::from("/bar.txt")); + /// + /// buf.set_file_name("baz"); + /// assert!(buf == PathBuf::from("/baz")); + /// ``` + #[inline] + pub fn set_file_name(&mut self, file_name: impl AsRef) { + self.inner.set_file_name(file_name) + } + + /// Updates [`self.extension`](Path::extension) to `Some(extension)` or to [`None`] if + /// `extension` is empty. + /// + /// Returns `false` and does nothing if [`self.file_name`](Path::file_name) is [`None`], + /// returns `true` and updates the extension otherwise. + /// + /// If [`self.extension`](Path::extension) is [`None`], the extension is added; otherwise + /// it is replaced. + /// + /// If `extension` is the empty string, [`self.extension`](Path::extension) will be [`None`] + /// afterwards, not `Some("")`. + /// + /// # Caveats + /// + /// The new `extension` may contain dots and will be used in its entirety, + /// but only the part after the final dot will be reflected in + /// [`self.extension`](Path::extension). + /// + /// If the file stem contains internal dots and `extension` is empty, part of the + /// old file stem will be considered the new [`self.extension`](Path::extension). + /// + /// # Examples + /// + /// ``` + /// use nu_path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/feel/the"); + /// + /// p.set_extension("force"); + /// assert_eq!(Path::new("/feel/the.force"), p.as_path()); + /// + /// p.set_extension("dark.side"); + /// assert_eq!(Path::new("/feel/the.dark.side"), p.as_path()); + /// + /// p.set_extension("cookie"); + /// assert_eq!(Path::new("/feel/the.dark.cookie"), p.as_path()); + /// + /// p.set_extension(""); + /// assert_eq!(Path::new("/feel/the.dark"), p.as_path()); + /// + /// p.set_extension(""); + /// assert_eq!(Path::new("/feel/the"), p.as_path()); + /// + /// p.set_extension(""); + /// assert_eq!(Path::new("/feel/the"), p.as_path()); + /// ``` + #[inline] + pub fn set_extension(&mut self, extension: impl AsRef) -> bool { + self.inner.set_extension(extension) + } +} + +impl PathBuf { + /// Consumes a [`PathBuf`] and returns the, potentially relative, + /// underlying [`std::path::PathBuf`]. + /// + /// # Note + /// + /// Caution should be taken when using this function. Nushell keeps track of an emulated current + /// working directory, and using the [`std::path::PathBuf`] returned from this method + /// will likely use [`std::env::current_dir`] to resolve the path instead of + /// using the emulated current working directory. + /// + /// Instead, you should probably join this path onto the emulated current working directory. + /// Any [`AbsolutePath`] or [`CanonicalPath`] will also suffice. + /// + /// # Examples + /// + /// ``` + /// use nu_path::PathBuf; + /// + /// let p = PathBuf::from("test.txt"); + /// assert_eq!(std::path::PathBuf::from("test.txt"), p.into_relative_std_path_buf()); + /// ``` + #[inline] + pub fn into_relative_std_path_buf(self) -> std::path::PathBuf { + self.inner + } +} + +impl PathBuf { + /// Consumes a [`PathBuf`] and returns the underlying [`std::path::PathBuf`]. + /// + /// # Examples + /// + #[cfg_attr(not(windows), doc = "```")] + #[cfg_attr(windows, doc = "```no_run")] + /// use nu_path::AbsolutePathBuf; + /// + /// let p = AbsolutePathBuf::try_from("/test").unwrap(); + /// assert_eq!(std::path::PathBuf::from("/test"), p.into_std_path_buf()); + /// ``` + #[inline] + pub fn into_std_path_buf(self) -> std::path::PathBuf { + self.inner + } +} + +impl CanonicalPathBuf { + /// Consumes a [`CanonicalPathBuf`] and returns an [`AbsolutePathBuf`]. + /// + /// # Examples + /// + /// ```no_run + /// use nu_path::AbsolutePathBuf; + /// + /// let absolute = AbsolutePathBuf::try_from("/test").unwrap(); + /// let p = absolute.canonicalize().unwrap(); + /// assert_eq!(absolute, p.into_absolute()); + /// ``` + #[inline] + pub fn into_absolute(self) -> AbsolutePathBuf { + self.cast_into() + } +} + +impl Default for PathBuf { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Clone for PathBuf { + #[inline] + fn clone(&self) -> Self { + Self { + _form: PhantomData, + inner: self.inner.clone(), + } + } +} + +impl fmt::Debug for PathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl Deref for PathBuf { + type Target = Path; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_path() + } +} + +impl DerefMut for PathBuf { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let path: &mut std::path::Path = &mut self.inner; + let ptr = std::ptr::from_mut(path) as *mut Path; + unsafe { &mut *ptr } + } +} + +impl, To: PathForm> Borrow> for PathBuf { + #[inline] + fn borrow(&self) -> &Path { + self.cast() + } +} + +impl Borrow for PathBuf { + #[inline] + fn borrow(&self) -> &std::path::Path { + self.as_ref() + } +} + +impl Borrow for std::path::PathBuf { + #[inline] + fn borrow(&self) -> &Path { + self.as_ref() + } +} + +impl FromStr for PathBuf { + type Err = Infallible; + + #[inline] + fn from_str(s: &str) -> Result { + Ok(s.into()) + } +} + +impl FromStr for RelativePathBuf { + type Err = TryRelativeError; + + #[inline] + fn from_str(s: &str) -> Result { + s.try_into() + } +} + +impl FromStr for AbsolutePathBuf { + type Err = TryAbsoluteError; + + #[inline] + fn from_str(s: &str) -> Result { + s.try_into() + } +} + +impl> Extend

for PathBuf { + fn extend>(&mut self, iter: T) { + for path in iter { + self.push(path); + } + } +} + +impl> FromIterator

for PathBuf { + fn from_iter>(iter: T) -> Self { + let mut buf = Self::new_unchecked(std::path::PathBuf::new()); + buf.extend(iter); + buf + } +} + +impl<'a, Form: PathForm> IntoIterator for &'a PathBuf { + type Item = &'a OsStr; + + type IntoIter = std::path::Iter<'a>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[inline] +fn box_to_box_unchecked(path: Box>) -> Box> { + // Safety: `Path` and `Path` differ only by PhantomData tag. + let ptr = Box::into_raw(path) as *mut Path; + unsafe { Box::from_raw(ptr) } +} + +#[inline] +fn std_box_to_box(path: Box) -> Box> { + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let ptr = Box::into_raw(path) as *mut Path; + unsafe { Box::from_raw(ptr) } +} + +#[inline] +fn std_arc_to_arc(path: Arc) -> Arc> { + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let ptr = Arc::into_raw(path) as *mut Path; + unsafe { Arc::from_raw(ptr) } +} + +#[inline] +fn std_rc_to_rc(path: Rc) -> Rc> { + // Safety: `Path` is a repr(transparent) wrapper around `std::path::Path`. + let ptr = Rc::into_raw(path) as *mut Path; + unsafe { Rc::from_raw(ptr) } +} + +/* +================================================================================ + AsRef +================================================================================ +*/ + +// Here we match all `AsRef` implementations on `std::path::Path` and `std::path::PathBuf`, +// adding casting variations where possible. + +macro_rules! impl_as_ref { + ([$($from:ty),* $(,)?] => $to:ty |$self:ident| $cast:block) => { + $( + impl AsRef<$to> for $from { + #[inline] + fn as_ref(&$self) -> &$to $cast + } + )* + }; +} + +// === To and from crate types === + +impl, To: PathForm> AsRef> for Path { + #[inline] + fn as_ref(&self) -> &Path { + self.cast() + } +} + +impl, To: PathForm> AsRef> for PathBuf { + #[inline] + fn as_ref(&self) -> &Path { + self.cast() + } +} + +impl_as_ref!( + [ + Box, Box, Box, + Cow<'_, RelativePath>, Cow<'_, AbsolutePath>, Cow<'_, CanonicalPath>, + Rc, Rc, Rc, + Arc, Arc, Arc, + ] + => Path |self| { self.cast() } +); + +impl_as_ref!( + [Box, Cow<'_, CanonicalPath>, Rc, Arc] + => AbsolutePath |self| { self.cast() } +); + +// === To and from std::path types === + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &std::path::Path { + self.as_std_path() + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &std::path::Path { + self.as_std_path() + } +} + +impl_as_ref!( + [std::path::Path, std::path::PathBuf, std::path::Component<'_>] + => Path |self| { Path::new(self) } +); + +impl_as_ref!( + [Box, Cow<'_, std::path::Path>, Rc, Arc] + => Path |self| { Path::new(self.as_os_str()) } +); + +// === To and from string types === + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +impl_as_ref!([OsStr, OsString, Cow<'_, OsStr>, str, String] => Path |self| { Path::new(self) }); + +/* +================================================================================ + From +================================================================================ +*/ + +// Here we match all `From` implementations on `std::path::Path` and `std::path::PathBuf`, +// adding casting variations where possible. + +macro_rules! impl_from { + ([$($from:ty),* $(,)?] => $to:ty |$value:ident| $convert:block) => { + $( + impl From<$from> for $to { + #[inline] + fn from($value: $from) -> Self $convert + } + )* + }; + (<$form:ident> $from:ty => $to:ty |$value:ident| $convert:block) => { + impl<$form: PathForm> From<$from> for $to { + #[inline] + fn from($value: $from) -> Self $convert + } + }; +} + +macro_rules! impl_into_std { + (<$form:ident> $from:ty => [$($to:ty),* $(,)?] |$value:ident| $convert:block) => { + $( + impl<$form: IsAbsolute> From<$from> for $to { + #[inline] + fn from($value: $from) -> Self $convert + } + )* + }; +} + +// ===== Owned to Owned ===== + +// === To and from crate types === + +impl_from!([RelativePathBuf, AbsolutePathBuf, CanonicalPathBuf] => PathBuf + |buf| { buf.cast_into() } +); +impl_from!([CanonicalPathBuf] => AbsolutePathBuf |buf| { buf.cast_into() }); + +#[inline] +fn box_to_box, To: PathForm>(path: Box>) -> Box> { + box_to_box_unchecked(path) +} +impl_from!([Box, Box, Box] => Box + |path| { box_to_box(path) } +); +impl_from!([Box] => Box |path| { box_to_box(path) }); + +impl_from!( PathBuf => Box> |buf| { buf.into_boxed_path() }); +impl_from!([RelativePathBuf, AbsolutePathBuf, CanonicalPathBuf] => Box + |buf| { buf.into_boxed_path().into() } +); +impl_from!([CanonicalPathBuf] => Box |buf| { buf.into_boxed_path().into() }); + +impl_from!( Box> => PathBuf |path| { path.into_path_buf() }); +impl_from!([Box, Box, Box] => PathBuf + |path| { path.into_path_buf().into() } +); +impl_from!([Box] => AbsolutePathBuf |path| { path.into_path_buf().into() }); + +impl_from!( PathBuf => Cow<'_, Path> |buf| { Self::Owned(buf) }); +impl_from!([RelativePathBuf, AbsolutePathBuf, CanonicalPathBuf] => Cow<'_, Path> + |buf| { Self::Owned(buf.into()) } +); +impl_from!([CanonicalPathBuf] => Cow<'_, AbsolutePath> |buf| { Self::Owned(buf.into()) }); + +impl_from!( Cow<'_, Path> => PathBuf |cow| { cow.into_owned() }); +impl_from!([Cow<'_, RelativePath>, Cow<'_, AbsolutePath>, Cow<'_, CanonicalPath>] => PathBuf + |cow| { cow.into_owned().into() } +); +impl_from!([Cow<'_, CanonicalPath>] => AbsolutePathBuf |cow| { cow.into_owned().into() }); + +#[inline] +fn cow_to_box(cow: Cow<'_, From>) -> Box +where + From: ?Sized + ToOwned, + for<'a> &'a From: Into>, + From::Owned: Into>, + To: ?Sized, +{ + match cow { + Cow::Borrowed(path) => path.into(), + Cow::Owned(path) => path.into(), + } +} +impl_from!( Cow<'_, Path> => Box> |cow| { cow_to_box(cow) }); +impl_from!([Cow<'_, RelativePath>, Cow<'_, AbsolutePath>, Cow<'_, CanonicalPath>] => Box + |cow| { cow_to_box(cow) } +); +impl_from!([Cow<'_, CanonicalPath>] => Box |cow| { cow_to_box(cow) }); + +#[inline] +fn buf_to_arc, To: PathForm>(buf: PathBuf) -> Arc> { + std_arc_to_arc(buf.inner.into()) +} +impl_from!( PathBuf => Arc> |buf| { buf_to_arc(buf) }); +impl_from!([RelativePathBuf, AbsolutePathBuf, CanonicalPathBuf] => Arc + |buf| { buf_to_arc(buf) } +); +impl_from!([CanonicalPathBuf] => Arc |buf| { buf_to_arc(buf) }); + +#[inline] +fn buf_to_rc, To: PathForm>(buf: PathBuf) -> Rc> { + std_rc_to_rc(buf.inner.into()) +} +impl_from!( PathBuf => Rc> |buf| { buf_to_rc(buf) }); +impl_from!([RelativePathBuf, AbsolutePathBuf, CanonicalPathBuf] => Rc + |buf| { buf_to_rc(buf) } +); +impl_from!([CanonicalPathBuf] => Rc |buf| { buf_to_rc(buf) }); + +// === To and from std::path types === + +impl_into_std!( PathBuf => [std::path::PathBuf] |buf| { buf.inner }); +impl_into_std!( + PathBuf => [ + Box, Cow<'_, std::path::Path>, Arc, Rc + ] + |buf| { buf.inner.into() } +); +impl_into_std!( Box> => [std::path::PathBuf, Box] + |path| { path.inner.into() } +); + +impl_from!([std::path::PathBuf] => PathBuf |buf| { Self::new_unchecked(buf) }); +impl_from!([Box] => PathBuf |path| { Self::new_unchecked(path.into()) }); +impl_from!([Cow<'_, std::path::Path>] => PathBuf |cow| { Self::new_unchecked(cow.into()) }); + +impl From> for Box { + #[inline] + fn from(path: Box) -> Self { + std_box_to_box(path) + } +} +impl_from!([std::path::PathBuf] => Box |buf| { buf.into_boxed_path().into() }); +impl_from!([Cow<'_, std::path::Path>] => Box |cow| { cow_to_box(cow) }); + +// === To and from string types === + +impl_from!( PathBuf => OsString |buf| { buf.inner.into() }); +impl_from!([OsString, String] => PathBuf |s| { Self::new_unchecked(s.into()) }); + +// ===== Borrowed to Owned ===== + +// === To and from crate types === +// Here we also add casting conversions from `T: impl AsRef>` to `PathBuf`. + +impl, To: PathForm> From<&Path> for Box> { + #[inline] + fn from(path: &Path) -> Self { + std_box_to_box(path.inner.into()) + } +} + +impl<'a, Source: PathCast, To: PathForm> From<&'a Path> for Cow<'a, Path> { + #[inline] + fn from(path: &'a Path) -> Self { + path.cast().into() + } +} + +impl<'a, Source: PathCast, To: PathForm> From<&'a PathBuf> for Cow<'a, Path> { + #[inline] + fn from(buf: &'a PathBuf) -> Self { + buf.cast().into() + } +} + +impl, To: PathForm> From<&Path> for Arc> { + #[inline] + fn from(path: &Path) -> Self { + std_arc_to_arc(path.inner.into()) + } +} + +impl, To: PathForm> From<&Path> for Rc> { + #[inline] + fn from(path: &Path) -> Self { + std_rc_to_rc(path.inner.into()) + } +} + +impl> From<&T> for RelativePathBuf { + #[inline] + fn from(s: &T) -> Self { + Self::new_unchecked(s.as_ref().into()) + } +} + +impl> From<&T> for AbsolutePathBuf { + #[inline] + fn from(s: &T) -> Self { + Self::new_unchecked(s.as_ref().into()) + } +} + +impl> From<&T> for CanonicalPathBuf { + #[inline] + fn from(s: &T) -> Self { + Self::new_unchecked(s.as_ref().into()) + } +} + +// === To and from std::path types === + +impl_into_std!( + &Path => [Box, Arc, Rc] + |path| { path.inner.into() } +); + +impl<'a, Form: IsAbsolute> From<&'a Path> for Cow<'a, std::path::Path> { + #[inline] + fn from(path: &'a Path) -> Self { + path.inner.into() + } +} + +impl<'a, Form: IsAbsolute> From<&'a PathBuf> for Cow<'a, std::path::Path> { + #[inline] + fn from(buf: &'a PathBuf) -> Self { + Self::Borrowed(buf.as_ref()) + } +} + +impl_from!([&std::path::Path] => Box |path| { Path::new(path).into() }); + +// === To and from string types === + +impl> From<&T> for PathBuf { + #[inline] + fn from(s: &T) -> Self { + Self::new_unchecked(s.as_ref().into()) + } +} + +/* +================================================================================ + TryFrom +================================================================================ +*/ + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TryRelativeError; + +impl fmt::Display for TryRelativeError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "path was not a relative path") + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TryAbsoluteError; + +impl fmt::Display for TryAbsoluteError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "path was not an absolute path") + } +} + +// ===== Borrowed to borrowed ===== +// Here we match all `AsRef` implementations on `std::path::Path`. + +macro_rules! impl_try_from_borrowed_to_borrowed { + ([$($from:ty),* $(,)?], |$value:ident| $convert:block $(,)?) => { + $( + impl<'a> TryFrom<&'a $from> for &'a RelativePath { + type Error = TryRelativeError; + + #[inline] + fn try_from($value: &'a $from) -> Result $convert + } + + impl<'a> TryFrom<&'a $from> for &'a AbsolutePath { + type Error = TryAbsoluteError; + + #[inline] + fn try_from($value: &'a $from) -> Result $convert + } + )* + }; +} + +// === From crate types === + +impl<'a> TryFrom<&'a Path> for &'a RelativePath { + type Error = TryRelativeError; + + #[inline] + fn try_from(path: &'a Path) -> Result { + path.try_relative().map_err(|_| TryRelativeError) + } +} + +impl<'a> TryFrom<&'a Path> for &'a AbsolutePath { + type Error = TryAbsoluteError; + + #[inline] + fn try_from(path: &'a Path) -> Result { + path.try_absolute().map_err(|_| TryAbsoluteError) + } +} + +impl_try_from_borrowed_to_borrowed!([PathBuf], |buf| { Path::new(buf).try_into() }); + +// === From std::path types === + +impl_try_from_borrowed_to_borrowed!([std::path::Path], |path| { Path::new(path).try_into() }); +impl_try_from_borrowed_to_borrowed!([std::path::PathBuf], |buf| { Path::new(buf).try_into() }); +impl_try_from_borrowed_to_borrowed!([std::path::Component<'_>], |component| { + Path::new(component).try_into() +}); +impl_try_from_borrowed_to_borrowed!([std::path::Components<'_>], |components| { + Path::new(components).try_into() +}); +impl_try_from_borrowed_to_borrowed!([std::path::Iter<'_>], |iter| { Path::new(iter).try_into() }); + +// === From string types === + +impl_try_from_borrowed_to_borrowed!( + [OsStr, OsString, Cow<'_, OsStr>, str, String], + |s| { Path::new(s).try_into() }, +); + +// ===== Borrowed to Owned ===== +// Here we match all `From<&T>` implementations on `std::path::Path` and `std::path::PathBuf`. +// Note that to match `From<&T: AsRef>` on `std::path::PathBuf`, +// we add string conversions and a few others. + +macro_rules! impl_try_from_borrowed_to_owned { + ([$($from:ty),* $(,)?] => $rel:ty, $abs:ty $(,)?) => { + $( + impl TryFrom<&$from> for $rel { + type Error = TryRelativeError; + + #[inline] + fn try_from(path: &$from) -> Result { + let path: &RelativePath = path.try_into()?; + Ok(path.into()) + } + } + + impl TryFrom<&$from> for $abs { + type Error = TryAbsoluteError; + + #[inline] + fn try_from(path: &$from) -> Result { + let path: &AbsolutePath = path.try_into()?; + Ok(path.into()) + } + } + )* + }; + (<$life:lifetime> $from:ty => $rel:ty, $abs:ty $(,)?) => { + impl<$life> TryFrom<&$life $from> for $rel { + type Error = TryRelativeError; + + #[inline] + fn try_from(path: &$life $from) -> Result { + let path: &RelativePath = path.try_into()?; + Ok(path.into()) + } + } + + impl<$life> TryFrom<&$life $from> for $abs { + type Error = TryAbsoluteError; + + #[inline] + fn try_from(path: &$life $from) -> Result { + let path: &AbsolutePath = path.try_into()?; + Ok(path.into()) + } + } + }; +} + +// === From crate types === + +impl_try_from_borrowed_to_owned!([Path] => Box, Box); +impl_try_from_borrowed_to_owned!(<'a> Path => Cow<'a, RelativePath>, Cow<'a, AbsolutePath>); +impl_try_from_borrowed_to_owned!([Path] => Arc, Arc); +impl_try_from_borrowed_to_owned!([Path] => Rc, Rc); + +impl_try_from_borrowed_to_owned!([Path, PathBuf] => RelativePathBuf, AbsolutePathBuf); +impl_try_from_borrowed_to_owned!(<'a> PathBuf => Cow<'a, RelativePath>, Cow<'a, AbsolutePath>); + +// === From std::path types === + +impl_try_from_borrowed_to_owned!([std::path::Path] => Box, Box); + +impl_try_from_borrowed_to_owned!( + [std::path::Path, std::path::PathBuf, std::path::Component<'_>] + => RelativePathBuf, AbsolutePathBuf +); + +// === From string types === + +impl_try_from_borrowed_to_owned!( + [OsStr, OsString, Cow<'_, OsStr>, str, String] => RelativePathBuf, AbsolutePathBuf +); + +// ===== Owned to Owned ===== +// Here we match all `From` implementations on `std::path::Path` and `std::path::PathBuf` +// where `T` is an owned type. + +// === From crate types === + +impl TryFrom for RelativePathBuf { + type Error = PathBuf; + + #[inline] + fn try_from(buf: PathBuf) -> Result { + buf.try_into_relative() + } +} + +impl TryFrom for AbsolutePathBuf { + type Error = PathBuf; + + #[inline] + fn try_from(buf: PathBuf) -> Result { + buf.try_into_absolute() + } +} + +impl TryFrom> for RelativePathBuf { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_relative() { + Ok(Self::new_unchecked(path.inner.into())) + } else { + Err(path) + } + } +} + +impl TryFrom> for AbsolutePathBuf { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_absolute() { + Ok(Self::new_unchecked(path.inner.into())) + } else { + Err(path) + } + } +} + +impl TryFrom> for Box { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_relative() { + Ok(box_to_box_unchecked(path)) + } else { + Err(path) + } + } +} + +impl TryFrom> for Box { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_absolute() { + Ok(box_to_box_unchecked(path)) + } else { + Err(path) + } + } +} + +impl TryFrom for Box { + type Error = PathBuf; + + #[inline] + fn try_from(buf: PathBuf) -> Result { + RelativePathBuf::try_from(buf).map(Into::into) + } +} + +impl TryFrom for Box { + type Error = PathBuf; + + #[inline] + fn try_from(buf: PathBuf) -> Result { + AbsolutePathBuf::try_from(buf).map(Into::into) + } +} + +impl<'a> TryFrom> for RelativePathBuf { + type Error = Cow<'a, Path>; + + #[inline] + fn try_from(path: Cow<'a, Path>) -> Result { + match path { + Cow::Borrowed(path) => Self::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Self::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for AbsolutePathBuf { + type Error = Cow<'a, Path>; + + #[inline] + fn try_from(path: Cow<'a, Path>) -> Result { + match path { + Cow::Borrowed(path) => Self::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Self::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for Box { + type Error = Cow<'a, Path>; + + #[inline] + fn try_from(path: Cow<'a, Path>) -> Result { + match path { + Cow::Borrowed(path) => Box::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Box::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for Box { + type Error = Cow<'a, Path>; + + #[inline] + fn try_from(path: Cow<'a, Path>) -> Result { + match path { + Cow::Borrowed(path) => Box::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Box::try_from(path).map_err(Cow::Owned), + } + } +} + +// === From std::path types === + +impl TryFrom for RelativePathBuf { + type Error = std::path::PathBuf; + + #[inline] + fn try_from(buf: std::path::PathBuf) -> Result { + Self::try_from(PathBuf::from(buf)).map_err(|buf| buf.inner) + } +} + +impl TryFrom for AbsolutePathBuf { + type Error = std::path::PathBuf; + + #[inline] + fn try_from(buf: std::path::PathBuf) -> Result { + Self::try_from(PathBuf::from(buf)).map_err(|buf| buf.inner) + } +} + +impl TryFrom> for RelativePathBuf { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_relative() { + Ok(Self::new_unchecked(path.into())) + } else { + Err(path) + } + } +} + +impl TryFrom> for AbsolutePathBuf { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_absolute() { + Ok(Self::new_unchecked(path.into())) + } else { + Err(path) + } + } +} + +impl TryFrom> for Box { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_relative() { + Ok(std_box_to_box(path)) + } else { + Err(path) + } + } +} + +impl TryFrom> for Box { + type Error = Box; + + #[inline] + fn try_from(path: Box) -> Result { + if path.is_absolute() { + Ok(std_box_to_box(path)) + } else { + Err(path) + } + } +} + +impl TryFrom for Box { + type Error = std::path::PathBuf; + + #[inline] + fn try_from(buf: std::path::PathBuf) -> Result { + RelativePathBuf::try_from(buf).map(Into::into) + } +} + +impl TryFrom for Box { + type Error = std::path::PathBuf; + + #[inline] + fn try_from(buf: std::path::PathBuf) -> Result { + AbsolutePathBuf::try_from(buf).map(Into::into) + } +} + +impl<'a> TryFrom> for RelativePathBuf { + type Error = Cow<'a, std::path::Path>; + + #[inline] + fn try_from(path: Cow<'a, std::path::Path>) -> Result { + match path { + Cow::Borrowed(path) => Self::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Self::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for AbsolutePathBuf { + type Error = Cow<'a, std::path::Path>; + + #[inline] + fn try_from(path: Cow<'a, std::path::Path>) -> Result { + match path { + Cow::Borrowed(path) => Self::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Self::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for Box { + type Error = Cow<'a, std::path::Path>; + + #[inline] + fn try_from(path: Cow<'a, std::path::Path>) -> Result { + match path { + Cow::Borrowed(path) => Box::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Box::try_from(path).map_err(Cow::Owned), + } + } +} + +impl<'a> TryFrom> for Box { + type Error = Cow<'a, std::path::Path>; + + #[inline] + fn try_from(path: Cow<'a, std::path::Path>) -> Result { + match path { + Cow::Borrowed(path) => Box::try_from(path).map_err(|_| Cow::Borrowed(path)), + Cow::Owned(path) => Box::try_from(path).map_err(Cow::Owned), + } + } +} + +// === From string types === + +impl TryFrom for RelativePathBuf { + type Error = OsString; + + #[inline] + fn try_from(s: OsString) -> Result { + Self::try_from(PathBuf::from(s)).map_err(|buf| buf.into_os_string()) + } +} + +impl TryFrom for AbsolutePathBuf { + type Error = OsString; + + #[inline] + fn try_from(s: OsString) -> Result { + Self::try_from(PathBuf::from(s)).map_err(|buf| buf.into_os_string()) + } +} + +impl TryFrom for RelativePathBuf { + type Error = String; + + #[inline] + fn try_from(s: String) -> Result { + if Path::new(&s).is_relative() { + Ok(Self::new_unchecked(s.into())) + } else { + Err(s) + } + } +} + +impl TryFrom for AbsolutePathBuf { + type Error = String; + + #[inline] + fn try_from(s: String) -> Result { + if Path::new(&s).is_absolute() { + Ok(Self::new_unchecked(s.into())) + } else { + Err(s) + } + } +} + +/* +================================================================================ + PartialEq, Eq, PartialOrd, and Ord +================================================================================ +*/ + +// Here we match all `PartialEq` and `PartialOrd` implementations on `std::path::Path` +// and `std::path::PathBuf`, adding casting variations where possible. + +// === Between crate types === + +impl PartialEq for Path { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for Path {} + +impl PartialOrd for Path { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.inner.cmp(&other.inner)) + } +} + +impl Ord for Path { + fn cmp(&self, other: &Self) -> Ordering { + self.inner.cmp(&other.inner) + } +} + +impl Hash for Path { + fn hash(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl PartialEq for PathBuf { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for PathBuf {} + +impl PartialOrd for PathBuf { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.inner.cmp(&other.inner)) + } +} + +impl Ord for PathBuf { + fn cmp(&self, other: &Self) -> Ordering { + self.inner.cmp(&other.inner) + } +} + +impl Hash for PathBuf { + fn hash(&self, state: &mut H) { + self.inner.hash(state); + } +} + +macro_rules! impl_cmp { + (<$($life:lifetime),*> $lhs:ty, $rhs:ty) => { + impl<$($life,)* Form: PathForm> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + as PartialEq>::eq(self, other) + } + } + + impl<$($life,)* Form: PathForm> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + as PartialEq>::eq(self, other) + } + } + + impl<$($life,)* Form: PathForm> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + as PartialOrd>::partial_cmp(self, other) + } + } + + impl<$($life,)* Form: PathForm> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + as PartialOrd>::partial_cmp(self, other) + } + } + }; +} + +impl_cmp!(<> PathBuf, Path); +impl_cmp!(<'a> PathBuf, &'a Path); +impl_cmp!(<'a> Cow<'a, Path>, Path); +impl_cmp!(<'a, 'b> Cow<'a, Path>, &'b Path); +impl_cmp!(<'a> Cow<'a, Path>, PathBuf); + +macro_rules! impl_cmp_cast { + (<$($life:lifetime),*> $lhs:ty, $rhs:ty) => { + impl<$($life),*> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + ::eq(self.cast(), other.cast()) + } + } + + impl<$($life),*> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + ::eq(self.cast(), other.cast()) + } + } + + impl<$($life),*> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self.cast(), other.cast()) + } + } + + impl<$($life),*> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self.cast(), other.cast()) + } + } + }; +} + +impl_cmp_cast!(<> Path, RelativePath); +impl_cmp_cast!(<> Path, AbsolutePath); +impl_cmp_cast!(<> Path, CanonicalPath); +impl_cmp_cast!(<> AbsolutePath, CanonicalPath); +impl_cmp_cast!(<> PathBuf, RelativePathBuf); +impl_cmp_cast!(<> PathBuf, AbsolutePathBuf); +impl_cmp_cast!(<> PathBuf, CanonicalPathBuf); +impl_cmp_cast!(<> AbsolutePathBuf, CanonicalPathBuf); + +impl_cmp_cast!(<'a> &'a Path, RelativePath); +impl_cmp_cast!(<'a> &'a Path, AbsolutePath); +impl_cmp_cast!(<'a> &'a Path, CanonicalPath); +impl_cmp_cast!(<'a> &'a AbsolutePath, CanonicalPath); +impl_cmp_cast!(<'a> Path, &'a RelativePath); +impl_cmp_cast!(<'a> Path, &'a AbsolutePath); +impl_cmp_cast!(<'a> Path, &'a CanonicalPath); +impl_cmp_cast!(<'a> AbsolutePath, &'a CanonicalPath); + +impl_cmp_cast!(<> PathBuf, RelativePath); +impl_cmp_cast!(<> PathBuf, AbsolutePath); +impl_cmp_cast!(<> PathBuf, CanonicalPath); +impl_cmp_cast!(<> AbsolutePathBuf, CanonicalPath); +impl_cmp_cast!(<> RelativePathBuf, Path); +impl_cmp_cast!(<> AbsolutePathBuf, Path); +impl_cmp_cast!(<> CanonicalPathBuf, Path); +impl_cmp_cast!(<> CanonicalPathBuf, AbsolutePath); + +impl_cmp_cast!(<'a> PathBuf, &'a RelativePath); +impl_cmp_cast!(<'a> PathBuf, &'a AbsolutePath); +impl_cmp_cast!(<'a> PathBuf, &'a CanonicalPath); +impl_cmp_cast!(<'a> AbsolutePathBuf, &'a CanonicalPath); +impl_cmp_cast!(<'a> RelativePathBuf, &'a Path); +impl_cmp_cast!(<'a> AbsolutePathBuf, &'a Path); +impl_cmp_cast!(<'a> CanonicalPathBuf, &'a Path); +impl_cmp_cast!(<'a> CanonicalPathBuf, &'a AbsolutePath); + +impl_cmp_cast!(<'a> Cow<'a, Path>, RelativePath); +impl_cmp_cast!(<'a> Cow<'a, Path>, AbsolutePath); +impl_cmp_cast!(<'a> Cow<'a, Path>, CanonicalPath); +impl_cmp_cast!(<'a> Cow<'a, AbsolutePath>, CanonicalPath); +impl_cmp_cast!(<'a> Cow<'a, RelativePath>, Path); +impl_cmp_cast!(<'a> Cow<'a, AbsolutePath>, Path); +impl_cmp_cast!(<'a> Cow<'a, CanonicalPath>, Path); +impl_cmp_cast!(<'a> Cow<'a, CanonicalPath>, AbsolutePath); + +impl_cmp_cast!(<'a, 'b> Cow<'a, Path>, &'b RelativePath); +impl_cmp_cast!(<'a, 'b> Cow<'a, Path>, &'b AbsolutePath); +impl_cmp_cast!(<'a, 'b> Cow<'a, Path>, &'b CanonicalPath); +impl_cmp_cast!(<'a, 'b> Cow<'a, AbsolutePath>, &'b CanonicalPath); +impl_cmp_cast!(<'a, 'b> Cow<'a, RelativePath>, &'b Path); +impl_cmp_cast!(<'a, 'b> Cow<'a, AbsolutePath>, &'b Path); +impl_cmp_cast!(<'a, 'b> Cow<'a, CanonicalPath>, &'b Path); +impl_cmp_cast!(<'a, 'b> Cow<'a, CanonicalPath>, &'b AbsolutePath); + +impl_cmp_cast!(<'a> Cow<'a, Path>, RelativePathBuf); +impl_cmp_cast!(<'a> Cow<'a, Path>, AbsolutePathBuf); +impl_cmp_cast!(<'a> Cow<'a, Path>, CanonicalPathBuf); +impl_cmp_cast!(<'a> Cow<'a, AbsolutePath>, CanonicalPathBuf); +impl_cmp_cast!(<'a> Cow<'a, RelativePath>, PathBuf); +impl_cmp_cast!(<'a> Cow<'a, AbsolutePath>, PathBuf); +impl_cmp_cast!(<'a> Cow<'a, CanonicalPath>, PathBuf); +impl_cmp_cast!(<'a> Cow<'a, CanonicalPath>, AbsolutePathBuf); + +// === Between std::path types === + +macro_rules! impl_cmp_std { + (<$($life:lifetime),*> $lhs:ty, $rhs:ty) => { + impl<$($life,)* Form: PathForm> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + ::eq(self.as_ref(), other.as_any()) + } + } + + impl<$($life,)* Form: PathForm> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + ::eq(self.as_any(), other.as_ref()) + } + } + + impl<$($life,)* Form: PathForm> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self.as_ref(), other.as_any()) + } + } + + impl<$($life,)* Form: PathForm> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self.as_any(), other.as_ref()) + } + } + }; +} + +impl_cmp_std!(<> std::path::Path, Path); +impl_cmp_std!(<> std::path::PathBuf, Path); +impl_cmp_std!(<'a> std::path::PathBuf, &'a Path); +impl_cmp_std!(<'a> Cow<'a, std::path::Path>, Path); +impl_cmp_std!(<'a, 'b> Cow<'a, std::path::Path>, &'b Path); + +impl_cmp_std!(<> std::path::Path, PathBuf); +impl_cmp_std!(<'a> &'a std::path::Path, PathBuf); +impl_cmp_std!(<> std::path::PathBuf, PathBuf); +impl_cmp_std!(<'a> Cow<'a, std::path::Path>, PathBuf); + +// === Between string types === + +impl_cmp_std!(<> OsStr, Path); +impl_cmp_std!(<'a> OsStr, &'a Path); +impl_cmp_std!(<'a> &'a OsStr, Path); +impl_cmp_std!(<'a> Cow<'a, OsStr>, Path); +impl_cmp_std!(<'a, 'b> Cow<'b, OsStr>, &'a Path); +impl_cmp_std!(<> OsString, Path); +impl_cmp_std!(<'a> OsString, &'a Path); + +impl_cmp_std!(<> OsStr, PathBuf); +impl_cmp_std!(<'a> &'a OsStr, PathBuf); +impl_cmp_std!(<'a> Cow<'a, OsStr>, PathBuf); +impl_cmp_std!(<> OsString, PathBuf); diff --git a/typos.toml b/typos.toml index 92fecd5cb6..971aef7c15 100644 --- a/typos.toml +++ b/typos.toml @@ -17,6 +17,7 @@ extend-ignore-re = [ "--find ba\\b", "0x\\[ba be\\]", "\\)BaR'", + "fo�.txt", ] [type.rust.extend-words] From c5a00ca3f1d7457ce607648ebd216197acd992b2 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:14:15 -0700 Subject: [PATCH 28/38] update lock via cargo check to fix ci (#13233) --- Cargo.lock | 74 +++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b8f5273c8..9bb1992e02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2769,7 +2769,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.95.0" +version = "0.95.1" dependencies = [ "assert_cmd", "crossterm", @@ -2822,7 +2822,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.95.0" +version = "0.95.1" dependencies = [ "chrono", "crossterm", @@ -2857,7 +2857,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.95.0" +version = "0.95.1" dependencies = [ "indexmap", "miette", @@ -2869,7 +2869,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.95.0" +version = "0.95.1" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2894,7 +2894,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.95.0" +version = "0.95.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2906,7 +2906,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.95.0" +version = "0.95.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2917,7 +2917,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-ansi-term", "nu-engine", @@ -2929,7 +2929,7 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.95.0" +version = "0.95.1" dependencies = [ "alphanumeric-sort", "base64 0.22.1", @@ -3038,7 +3038,7 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.95.0" +version = "0.95.1" dependencies = [ "convert_case", "proc-macro-error", @@ -3049,7 +3049,7 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-glob", "nu-path", @@ -3059,7 +3059,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.95.0" +version = "0.95.1" dependencies = [ "ansi-str", "anyhow", @@ -3084,14 +3084,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.95.0" +version = "0.95.1" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.95.0" +version = "0.95.1" dependencies = [ "linked-hash-map", "num-traits", @@ -3101,7 +3101,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.95.0" +version = "0.95.1" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3122,7 +3122,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.95.0" +version = "0.95.1" dependencies = [ "bytesize", "chrono", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.95.0" +version = "0.95.1" dependencies = [ "dirs-next", "omnipath", @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.95.0" +version = "0.95.1" dependencies = [ "log", "nix", @@ -3162,7 +3162,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.95.0" +version = "0.95.1" dependencies = [ "interprocess", "log", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.95.0" +version = "0.95.1" dependencies = [ "log", "nu-engine", @@ -3191,7 +3191,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.95.0" +version = "0.95.1" dependencies = [ "bincode", "nu-protocol", @@ -3203,7 +3203,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3221,7 +3221,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.95.0" +version = "0.95.1" dependencies = [ "heapless", "nu-ansi-term", @@ -3230,7 +3230,7 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.95.0" +version = "0.95.1" dependencies = [ "brotli", "byte-unit", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.95.0" +version = "0.95.1" dependencies = [ "log", "miette", @@ -3274,7 +3274,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.95.0" +version = "0.95.1" dependencies = [ "chrono", "itertools 0.12.1", @@ -3292,7 +3292,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.95.0" +version = "0.95.1" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3306,7 +3306,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-utils", "unicode-width", @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-glob", "nu-path", @@ -3326,7 +3326,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.95.0" +version = "0.95.1" dependencies = [ "crossterm_winapi", "log", @@ -3352,7 +3352,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3362,7 +3362,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.95.0" +version = "0.95.1" dependencies = [ "eml-parser", "ical", @@ -3375,7 +3375,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.95.0" +version = "0.95.1" dependencies = [ "git2", "nu-plugin", @@ -3384,7 +3384,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.95.0" +version = "0.95.1" dependencies = [ "nu-plugin", "nu-protocol", @@ -3393,7 +3393,7 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.95.0" +version = "0.95.1" dependencies = [ "chrono", "chrono-tz 0.9.0", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.95.0" +version = "0.95.1" dependencies = [ "gjson", "nu-plugin", @@ -3436,7 +3436,7 @@ dependencies = [ [[package]] name = "nu_plugin_stress_internals" -version = "0.95.0" +version = "0.95.1" dependencies = [ "interprocess", "serde", @@ -3562,7 +3562,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.95.0" +version = "0.95.1" dependencies = [ "chrono", "fancy-regex", From 38ecb6d3805cbfc7750367435ceeb4c19526f4d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 06:43:46 +0000 Subject: [PATCH 29/38] Bump uuid from 1.8.0 to 1.9.1 (#13227) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/nu_plugin_polars/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bb1992e02..a6d3457335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6493,9 +6493,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60" [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "getrandom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3e02e7e066..c88eee88ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,7 +172,7 @@ uu_mv = "0.0.26" uu_whoami = "0.0.26" uu_uname = "0.0.26" uucore = "0.0.26" -uuid = "1.8.0" +uuid = "1.9.1" v_htmlescape = "0.15.0" wax = "0.6" which = "6.0.0" diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 77bb84e988..09906732b7 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -36,7 +36,7 @@ polars-ops = { version = "0.40"} polars-plan = { version = "0.40", features = ["regex"]} polars-utils = { version = "0.40"} typetag = "0.2" -uuid = { version = "1.7", features = ["v4", "serde"] } +uuid = { version = "1.9", features = ["v4", "serde"] } [dependencies.polars] features = [ From 0fd0e36be87e51f298195791b2627a0fcf5a81ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:44:31 +0800 Subject: [PATCH 30/38] Bump softprops/action-gh-release from 2.0.5 to 2.0.6 (#13230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.5 to 2.0.6.

Release notes

Sourced from softprops/action-gh-release's releases.

v2.0.6

maintenance release with updated dependencies

Changelog

Sourced from softprops/action-gh-release's changelog.

2.0.6

  • maintenance release with updated dependencies
Commits
  • a74c6b7 update changelog
  • b909f76 update dist/index.js
  • e49d08f chore(deps): bump glob from 8.0.3 to 10.4.2
  • f12ad25 chore(deps): bump @​octokit/plugin-throttling from 4.3.2 to 9.3.0
  • 7039a82 chore: release 2.0.6
  • f9c2b6c chore: update deps and run build
  • 73738a6 chore(deps): bump node dep and @types/node
  • a500a35 Bump ts-jest from 29.0.3 to 29.1.4 (#459)
  • See full diff in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | softprops/action-gh-release | [< 0.2, > 0.1.13] |
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=softprops/action-gh-release&package-manager=github_actions&previous-version=2.0.5&new-version=2.0.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nightly-build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 9a30cae614..31533cc3d8 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -161,7 +161,7 @@ jobs: # REF: https://github.com/marketplace/actions/gh-release # Create a release only in nushell/nightly repo - name: Publish Archive - uses: softprops/action-gh-release@v2.0.5 + uses: softprops/action-gh-release@v2.0.6 if: ${{ startsWith(github.repository, 'nushell/nightly') }} with: prerelease: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31fb1197a3..208acd42b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,7 +91,7 @@ jobs: # REF: https://github.com/marketplace/actions/gh-release - name: Publish Archive - uses: softprops/action-gh-release@v2.0.5 + uses: softprops/action-gh-release@v2.0.6 if: ${{ startsWith(github.ref, 'refs/tags/') }} with: draft: true From b679c2bfa255e6153c5d962cbd541d7a63ba3cab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:44:50 +0800 Subject: [PATCH 31/38] Bump crate-ci/typos from 1.22.7 to 1.22.9 (#13229) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.7 to 1.22.9.
Release notes

Sourced from crate-ci/typos's releases.

v1.22.9

[1.22.9] - 2024-06-22

Fixes

  • Stop correcting reoccurrence

v1.22.8

[1.22.8] - 2024-06-21

Features

  • (action) Add Arm, Mac support
Changelog

Sourced from crate-ci/typos's changelog.

[1.22.9] - 2024-06-22

Fixes

  • Stop correcting reoccurrence

[1.22.8] - 2024-06-21

Features

  • (action) Add Arm, Mac support
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.22.7&new-version=1.22.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 33e7ac0945..95fc51b970 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.22.7 + uses: crate-ci/typos@v1.22.9 From 8a7a407627d3c1f82f1cbde7fccfef278eae4a04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 22:48:11 +0000 Subject: [PATCH 32/38] Bump ratatui from 0.26.2 to 0.26.3 (#13228) --- Cargo.lock | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6d3457335..f5c20e482d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.11.0", "lazy_static", "lazycell", "proc-macro2", @@ -1995,12 +1995,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "indoc" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" - [[package]] name = "inotify" version = "0.9.6" @@ -4752,21 +4746,21 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" dependencies = [ "bitflags 2.5.0", "cassowary", "compact_str", "crossterm", - "indoc", "itertools 0.12.1", "lru", "paste", "stability", "strum", "unicode-segmentation", + "unicode-truncate", "unicode-width", ] @@ -6314,6 +6308,16 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +dependencies = [ + "itertools 0.12.1", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.12" From 020f4436d95a62003ec10e0ba65d446840b6046a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 22:48:45 +0000 Subject: [PATCH 33/38] Bump shadow-rs from 0.28.0 to 0.29.0 (#13226) --- Cargo.lock | 4 ++-- crates/nu-cmd-lang/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5c20e482d..cd7fe99800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5435,9 +5435,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d75516bdaee8f640543ad1f6e292448c23ce57143f812c3736ab4b0874383df" +checksum = "0a600f795d0894cda22235b44eea4b85c2a35b405f65523645ac8e35b306817a" dependencies = [ "const_format", "is_debug", diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 262248e6d9..16ac1b893a 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -18,10 +18,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.95.1" } itertools = { workspace = true } -shadow-rs = { version = "0.28", default-features = false } +shadow-rs = { version = "0.29", default-features = false } [build-dependencies] -shadow-rs = { version = "0.28", default-features = false } +shadow-rs = { version = "0.29", default-features = false } [features] mimalloc = [] From 58e8ea6084831cdc3247b848a0004a41c1312375 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:49:52 -0400 Subject: [PATCH 34/38] Update and add ls examples (#13222) # Description Based on #13219, added several examples to `ls` doc to demonstrate recursive directory listings. List of changes in this PR: * Add example for `ls **/*` to demonstrate recursive listing using glob pattern * Add example for `ls ...(glob )`... to demonstrate recursive listing using glob command * Remove `-s` from an example where it had no use (since it was based on the current directory and was not recursive) * Update the description of `ls -a ~ `... to clarify that it lists the full path of directories * Update the description of `ls -as ~ `... (the difference being the `-s`) to clarify that it lists only the filenames, not paths. # User-Facing Changes Help only # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting N/A --- crates/nu-command/src/filesystem/ls.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index a89cf04ed6..7eee57cfb6 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -175,20 +175,32 @@ impl Command for Ls { }, Example { description: "List files and directories whose name do not contain 'bar'", - example: "ls -s | where name !~ bar", + example: "ls | where name !~ bar", result: None, }, Example { - description: "List all dirs in your home directory", + description: "List the full path of all dirs in your home directory", example: "ls -a ~ | where type == dir", result: None, }, Example { description: - "List all dirs in your home directory which have not been modified in 7 days", + "List only the names (not paths) of all dirs in your home directory which have not been modified in 7 days", example: "ls -as ~ | where type == dir and modified < ((date now) - 7day)", result: None, }, + Example { + description: + "Recursively list all files and subdirectories under the current directory using a glob pattern", + example: "ls -a **/*", + result: None, + }, + Example { + description: + "Recursively list *.rs and *.toml files using the glob command", + example: "ls ...(glob **/*.{rs,toml})", + result: None, + }, Example { description: "List given paths and show directories themselves", example: "['/path/to/directory' '/path/to/file'] | each {|| ls -D $in } | flatten", From 198aedb6c2bee1a9ba43b3823381d37f5e5bd607 Mon Sep 17 00:00:00 2001 From: Piepmatz Date: Thu, 27 Jun 2024 00:50:14 +0200 Subject: [PATCH 35/38] Use `IntoValue` and `FromValue` derive macros in `nu_plugin_example` for example usage (#13220) # Description The derive macros provided by #13031 are very useful for plugin authors. In this PR I made use of these macros for two commands. # User-Facing Changes # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting This Example usage could be highlighted in the changelog for plugin authors as this is probably very useful for them. --- .../nu_plugin_example/src/commands/config.rs | 35 +++++++++++++++++-- crates/nu_plugin_example/src/commands/two.rs | 22 ++++++++---- tests/plugins/config.rs | 35 +++++-------------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/crates/nu_plugin_example/src/commands/config.rs b/crates/nu_plugin_example/src/commands/config.rs index f549bd324f..7716e27537 100644 --- a/crates/nu_plugin_example/src/commands/config.rs +++ b/crates/nu_plugin_example/src/commands/config.rs @@ -1,10 +1,37 @@ +use std::path::PathBuf; + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; -use nu_protocol::{Category, LabeledError, Signature, Type, Value}; +use nu_protocol::{Category, FromValue, LabeledError, Signature, Spanned, Type, Value}; use crate::ExamplePlugin; pub struct Config; +/// Example config struct. +/// +/// Using the `FromValue` derive macro, structs can be easily loaded from [`Value`]s, +/// similar to serde's `Deserialize` macro. +/// This is handy for plugin configs or piped data. +/// All fields must implement [`FromValue`]. +/// For [`Option`] fields, they can be omitted in the config. +/// +/// This example shows that nested and spanned data work too, so you can describe nested +/// structures and get spans of values wrapped in [`Spanned`]. +/// Since this config uses only `Option`s, no field is required in the config. +#[allow(dead_code)] +#[derive(Debug, FromValue)] +struct PluginConfig { + path: Option>, + nested: Option, +} + +#[allow(dead_code)] +#[derive(Debug, FromValue)] +struct SubConfig { + bool: bool, + string: String, +} + impl SimplePluginCommand for Config { type Plugin = ExamplePlugin; @@ -39,7 +66,11 @@ impl SimplePluginCommand for Config { ) -> Result { let config = engine.get_plugin_config()?; match config { - Some(config) => Ok(config.clone()), + Some(value) => { + let config = PluginConfig::from_value(value.clone())?; + println!("got config {config:?}"); + Ok(value) + } None => Err(LabeledError::new("No config sent").with_label( "configuration for this plugin was not found in `$env.config.plugins.example`", call.head, diff --git a/crates/nu_plugin_example/src/commands/two.rs b/crates/nu_plugin_example/src/commands/two.rs index fcf2bf75ff..e02465e8b7 100644 --- a/crates/nu_plugin_example/src/commands/two.rs +++ b/crates/nu_plugin_example/src/commands/two.rs @@ -1,5 +1,5 @@ use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; -use nu_protocol::{record, Category, LabeledError, Signature, SyntaxShape, Value}; +use nu_protocol::{Category, IntoValue, LabeledError, Signature, SyntaxShape, Value}; use crate::ExamplePlugin; @@ -38,14 +38,22 @@ impl SimplePluginCommand for Two { ) -> Result { plugin.print_values(2, call, input)?; + // Use the IntoValue derive macro and trait to easily design output data. + #[derive(IntoValue)] + struct Output { + one: i64, + two: i64, + three: i64, + } + let vals = (0..10i64) .map(|i| { - let record = record! { - "one" => Value::int(i, call.head), - "two" => Value::int(2 * i, call.head), - "three" => Value::int(3 * i, call.head), - }; - Value::record(record, call.head) + Output { + one: i, + two: 2 * i, + three: 3 * i, + } + .into_value(call.head) }) .collect(); diff --git a/tests/plugins/config.rs b/tests/plugins/config.rs index 44f2797ceb..b110d2a7fd 100644 --- a/tests/plugins/config.rs +++ b/tests/plugins/config.rs @@ -1,27 +1,5 @@ use nu_test_support::nu_with_plugins; -#[test] -fn closure() { - let actual = nu_with_plugins!( - cwd: "tests", - plugin: ("nu_plugin_example"), - r#" - $env.env_value = "value from env" - - $env.config = { - plugins: { - example: {|| - $env.env_value - } - } - } - example config - "# - ); - - assert!(actual.out.contains("value from env")); -} - #[test] fn none() { let actual = nu_with_plugins!( @@ -34,7 +12,7 @@ fn none() { } #[test] -fn record() { +fn some() { let actual = nu_with_plugins!( cwd: "tests", plugin: ("nu_plugin_example"), @@ -42,8 +20,11 @@ fn record() { $env.config = { plugins: { example: { - key1: "value" - key2: "other" + path: "some/path", + nested: { + bool: true, + string: "Hello Example!" + } } } } @@ -51,6 +32,6 @@ fn record() { "# ); - assert!(actual.out.contains("value")); - assert!(actual.out.contains("other")); + assert!(actual.out.contains("some/path")); + assert!(actual.out.contains("Hello Example!")); } From ee74ec742385e0e6608edf3516f6a0cb97ef863e Mon Sep 17 00:00:00 2001 From: goldfish <37319612+ito-hiroki@users.noreply.github.com> Date: Thu, 27 Jun 2024 07:51:47 +0900 Subject: [PATCH 36/38] Make the subcommands (`from {csv, tsv, ssv}`) 0-based for consistency (#13209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description fixed #11678 The sub-commands of from command (`from {csv, tsv, ssv}`) name columns starting from index 0. This behaviour is inconsistent with other commands such as `detect columns`. This PR makes the subcommands index 0-based. # User-Facing Changes The subcommands (`from {csv, tsv, ssv}`) return a table with the columns starting at index 0 if no header data is passed. ``` ~/Development/nushell> "foo bar baz" | from ssv -n -m 1 ╭───┬─────────┬─────────┬─────────╮ │ # │ column0 │ column1 │ column2 │ ├───┼─────────┼─────────┼─────────┤ │ 0 │ foo │ bar │ baz │ ╰───┴─────────┴─────────┴─────────╯ ~/Development/nushell> "foo,bar,baz" | from csv -n ╭───┬─────────┬─────────┬─────────╮ │ # │ column0 │ column1 │ column2 │ ├───┼─────────┼─────────┼─────────┤ │ 0 │ foo │ bar │ baz │ ╰───┴─────────┴─────────┴─────────╯ ~/Development/nushell> "foo\tbar\tbaz" | from tsv -n ╭───┬─────────┬─────────┬─────────╮ │ # │ column0 │ column1 │ column2 │ ├───┼─────────┼─────────┼─────────┤ │ 0 │ foo │ bar │ baz │ ╰───┴─────────┴─────────┴─────────╯ ``` # Tests + Formatting When I ran tests, `commands::touch::change_file_mtime_to_reference` failed with the following error. The error also occurs in the master branch, so it's probably unrelated to these changes. (maybe a problem with my dev environment) ``` $ toolkit check pr ~~~~~~~~ failures: ---- commands::touch::change_file_mtime_to_reference stdout ---- === stderr thread 'commands::touch::change_file_mtime_to_reference' panicked at crates/nu-command/tests/commands/touch.rs:298:9: assertion `left == right` failed left: SystemTime { tv_sec: 1719149697, tv_nsec: 57576929 } right: SystemTime { tv_sec: 1719149697, tv_nsec: 78219489 } failures: commands::touch::change_file_mtime_to_reference test result: FAILED. 1533 passed; 1 failed; 32 ignored; 0 measured; 0 filtered out; finished in 10.87s error: test failed, to rerun pass `-p nu-command --test main` - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :red_circle: `toolkit test` - :black_circle: `toolkit test stdlib` ``` # After Submitting nothing --- .../nu-command/src/formats/from/delimited.rs | 2 +- crates/nu-command/src/formats/from/ssv.rs | 44 +++++++++---------- .../tests/format_conversions/csv.rs | 2 +- .../tests/format_conversions/ssv.rs | 4 +- .../tests/format_conversions/tsv.rs | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 853f3bd83e..8f84890645 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -39,7 +39,7 @@ fn from_delimited_stream( .from_reader(input_reader); let headers = if noheaders { - (1..=reader + (0..reader .headers() .map_err(|err| from_csv_error(err, span))? .len()) diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs index 5efb2a6c3b..1d17dfc69d 100644 --- a/crates/nu-command/src/formats/from/ssv.rs +++ b/crates/nu-command/src/formats/from/ssv.rs @@ -52,12 +52,12 @@ impl Command for FromSsv { Value::test_list( vec![ Value::test_record(record! { - "column1" => Value::test_string("FOO"), - "column2" => Value::test_string("BAR"), + "column0" => Value::test_string("FOO"), + "column1" => Value::test_string("BAR"), }), Value::test_record(record! { - "column1" => Value::test_string("1"), - "column2" => Value::test_string("2"), + "column0" => Value::test_string("1"), + "column1" => Value::test_string("2"), }), ], ) @@ -170,7 +170,7 @@ fn parse_aligned_columns<'a>( let headers: Vec<(String, usize)> = indices .iter() .enumerate() - .map(|(i, position)| (format!("column{}", i + 1), *position)) + .map(|(i, position)| (format!("column{}", i), *position)) .collect(); construct(ls.iter().map(|s| s.to_owned()), headers) @@ -215,7 +215,7 @@ fn parse_separated_columns<'a>( let parse_without_headers = |ls: Vec<&str>| { let num_columns = ls.iter().map(|r| r.len()).max().unwrap_or(0); - let headers = (1..=num_columns) + let headers = (0..=num_columns) .map(|i| format!("column{i}")) .collect::>(); collect(headers, ls.into_iter(), separator) @@ -370,9 +370,9 @@ mod tests { assert_eq!( result, vec![ - vec![owned("column1", "a"), owned("column2", "b")], - vec![owned("column1", "1"), owned("column2", "2")], - vec![owned("column1", "3"), owned("column2", "4")] + vec![owned("column0", "a"), owned("column1", "b")], + vec![owned("column0", "1"), owned("column1", "2")], + vec![owned("column0", "3"), owned("column1", "4")] ] ); } @@ -484,25 +484,25 @@ mod tests { result, vec![ vec![ - owned("column1", "a multi-word value"), - owned("column2", "b"), - owned("column3", ""), - owned("column4", "d"), - owned("column5", "") - ], - vec![ - owned("column1", "1"), + owned("column0", "a multi-word value"), + owned("column1", "b"), owned("column2", ""), - owned("column3", "3-3"), - owned("column4", "4"), - owned("column5", "") + owned("column3", "d"), + owned("column4", "") ], vec![ + owned("column0", "1"), + owned("column1", ""), + owned("column2", "3-3"), + owned("column3", "4"), + owned("column4", "") + ], + vec![ + owned("column0", ""), owned("column1", ""), owned("column2", ""), owned("column3", ""), - owned("column4", ""), - owned("column5", "last") + owned("column4", "last") ], ] ); diff --git a/crates/nu-command/tests/format_conversions/csv.rs b/crates/nu-command/tests/format_conversions/csv.rs index a9be76d5c3..f10a84b672 100644 --- a/crates/nu-command/tests/format_conversions/csv.rs +++ b/crates/nu-command/tests/format_conversions/csv.rs @@ -284,7 +284,7 @@ fn from_csv_text_skipping_headers_to_table() { r#" open los_tres_amigos.txt | from csv --noheaders - | get column3 + | get column2 | length "# )); diff --git a/crates/nu-command/tests/format_conversions/ssv.rs b/crates/nu-command/tests/format_conversions/ssv.rs index a673b122fb..5a62f796f7 100644 --- a/crates/nu-command/tests/format_conversions/ssv.rs +++ b/crates/nu-command/tests/format_conversions/ssv.rs @@ -74,7 +74,7 @@ fn from_ssv_text_treating_first_line_as_data_with_flag() { open oc_get_svc.txt | from ssv --noheaders -a | first - | get column1 + | get column0 "# )); @@ -84,7 +84,7 @@ fn from_ssv_text_treating_first_line_as_data_with_flag() { open oc_get_svc.txt | from ssv --noheaders | first - | get column1 + | get column0 "# )); diff --git a/crates/nu-command/tests/format_conversions/tsv.rs b/crates/nu-command/tests/format_conversions/tsv.rs index be57c60242..31740ef8d9 100644 --- a/crates/nu-command/tests/format_conversions/tsv.rs +++ b/crates/nu-command/tests/format_conversions/tsv.rs @@ -207,7 +207,7 @@ fn from_tsv_text_skipping_headers_to_table() { r#" open los_tres_amigos.txt | from tsv --noheaders - | get column3 + | get column2 | length "# )); From 46ed69ab126015375d5163972ae321715f34874b Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Thu, 27 Jun 2024 01:49:44 +0200 Subject: [PATCH 37/38] Add `char nul` (#13241) # Description Adds `char nul`, `char null_byte` and `char zero_byte` named characters. All of them generate 0x00 byte. # User-Facing Changes Three new named characters are added: * `char nul` - generates 0x00 byte * `char null_byte` - generates 0x00 byte * `char zero_byte` - generates 0x00 byte --- crates/nu-command/src/strings/char_.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index dd7b5cf39b..6834134b26 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -19,6 +19,9 @@ static CHAR_MAP: Lazy> = Lazy::new(|| { // These are some regular characters that either can't be used or // it's just easier to use them like this. + "nul" => '\x00'.to_string(), // nul character, 0x00 + "null_byte" => '\x00'.to_string(), // nul character, 0x00 + "zero_byte" => '\x00'.to_string(), // nul character, 0x00 // This are the "normal" characters section "newline" => '\n'.to_string(), "enter" => '\n'.to_string(), From 0d79b637118ac53f27a829fdfbd930f434a6bf0e Mon Sep 17 00:00:00 2001 From: suimong Date: Thu, 27 Jun 2024 22:46:10 +0800 Subject: [PATCH 38/38] Fix find command output bug in the case of taking ByteStream input. (#13246) fixes #13245 # Description In addition to addressing #13245, this PR also updated one of the doc example to the `find` command to document that non-regex mode is case insensitive, which may surprise some users. # User-Facing Changes # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting --------- Co-authored-by: Ben Yang Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-command/src/filters/find.rs | 11 ++++++----- crates/nu-command/tests/commands/find.rs | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index dfdef66969..af626c1e75 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -69,9 +69,9 @@ impl Command for Find { result: None, }, Example { - description: "Search and highlight text for a term in a string", - example: r#"'Cargo.toml' | find toml"#, - result: Some(Value::test_string("\u{1b}[37mCargo.\u{1b}[0m\u{1b}[41;37mtoml\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_owned())), + description: "Search and highlight text for a term in a string. Note that regular search is case insensitive", + example: r#"'Cargo.toml' | find cargo"#, + result: Some(Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mCargo\u{1b}[0m\u{1b}[37m.toml\u{1b}[0m".to_owned())), }, Example { description: "Search a number or a file size in a list of numbers", @@ -457,9 +457,10 @@ fn find_with_rest_and_highlight( let mut output: Vec = vec![]; for line in lines { - let line = line?.to_lowercase(); + let line = line?; + let lower_val = line.to_lowercase(); for term in &terms { - if line.contains(term) { + if lower_val.contains(term) { output.push(Value::string( highlight_search_string( &line, diff --git a/crates/nu-command/tests/commands/find.rs b/crates/nu-command/tests/commands/find.rs index ed811f2a57..89bf2d9f39 100644 --- a/crates/nu-command/tests/commands/find.rs +++ b/crates/nu-command/tests/commands/find.rs @@ -17,6 +17,16 @@ fn find_with_list_search_with_char() { assert_eq!(actual.out, "[\"\\u001b[37m\\u001b[0m\\u001b[41;37ml\\u001b[0m\\u001b[37marry\\u001b[0m\",\"\\u001b[37mcur\\u001b[0m\\u001b[41;37ml\\u001b[0m\\u001b[37my\\u001b[0m\"]"); } +#[test] +fn find_with_bytestream_search_with_char() { + let actual = + nu!("\"ABC\" | save foo.txt; let res = open foo.txt | find abc; rm foo.txt; $res | get 0"); + assert_eq!( + actual.out, + "\u{1b}[37m\u{1b}[0m\u{1b}[41;37mABC\u{1b}[0m\u{1b}[37m\u{1b}[0m" + ) +} + #[test] fn find_with_list_search_with_number() { let actual = nu!("[1 2 3 4 5] | find 3 | get 0");