From 6e1118681deb507670994508b648117dc435867b Mon Sep 17 00:00:00 2001 From: Solomon Date: Sun, 17 Nov 2024 17:01:52 -0700 Subject: [PATCH 01/43] make command signature parsing more strict (#14309) # User-Facing Changes The parser now errors on more invalid command signatures: ```nushell # expected parameter or flag def foo [ bar: int: ] {} # expected type def foo [ bar: = ] {} def foo [ bar: ] {} # expected default value def foo [ bar = ] {} ``` --- crates/nu-command/tests/commands/def.rs | 29 ++++++++++++++++++++++++- crates/nu-parser/src/parser.rs | 25 ++++++++++++++++----- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/tests/commands/def.rs b/crates/nu-command/tests/commands/def.rs index c66869b479..0705c0085f 100644 --- a/crates/nu-command/tests/commands/def.rs +++ b/crates/nu-command/tests/commands/def.rs @@ -2,6 +2,13 @@ use nu_test_support::nu; use nu_test_support::playground::Playground; use std::fs; +#[test] +fn def_with_trailing_comma() { + let actual = nu!("def test-command [ foo: int, ] { $foo }; test-command 1"); + + assert!(actual.out == "1"); +} + #[test] fn def_with_comment() { Playground::setup("def_with_comment", |dirs, _| { @@ -72,6 +79,13 @@ fn def_errors_with_comma_before_equals() { assert!(actual.err.contains("expected parameter")); } +#[test] +fn def_errors_with_colon_before_equals() { + let actual = nu!("def test-command [ foo: = 1 ] {}"); + + assert!(actual.err.contains("expected type")); +} + #[test] fn def_errors_with_comma_before_colon() { let actual = nu!("def test-command [ foo, : int ] {}"); @@ -85,7 +99,6 @@ fn def_errors_with_multiple_colons() { assert!(actual.err.contains("expected type")); } -#[ignore = "This error condition is not implemented yet"] #[test] fn def_errors_with_multiple_types() { let actual = nu!("def test-command [ foo:int:string ] {}"); @@ -93,6 +106,20 @@ fn def_errors_with_multiple_types() { assert!(actual.err.contains("expected parameter")); } +#[test] +fn def_errors_with_trailing_colon() { + let actual = nu!("def test-command [ foo: int: ] {}"); + + assert!(actual.err.contains("expected parameter")); +} + +#[test] +fn def_errors_with_trailing_default_value() { + let actual = nu!("def test-command [ foo: int = ] {}"); + + assert!(actual.err.contains("expected default value")); +} + #[test] fn def_errors_with_multiple_commas() { let actual = nu!("def test-command [ foo,,bar ] {}"); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e4d3765e17..7db2e112bf 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3392,6 +3392,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> Arg, AfterCommaArg, Type, + AfterType, DefaultValue, } @@ -3425,7 +3426,9 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> let mut args: Vec = vec![]; let mut parse_mode = ParseMode::Arg; - for token in &output { + for (index, token) in output.iter().enumerate() { + let last_token = index == output.len() - 1; + match token { Token { contents: crate::TokenContents::Item | crate::TokenContents::AssignmentOperator, @@ -3437,10 +3440,12 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> // The : symbol separates types if contents == b":" { match parse_mode { + ParseMode::Arg if last_token => working_set + .error(ParseError::Expected("type", Span::new(span.end, span.end))), ParseMode::Arg => { parse_mode = ParseMode::Type; } - ParseMode::AfterCommaArg => { + ParseMode::AfterCommaArg | ParseMode::AfterType => { working_set.error(ParseError::Expected("parameter or flag", span)); } ParseMode::Type | ParseMode::DefaultValue => { @@ -3452,9 +3457,15 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> // The = symbol separates a variable from its default value else if contents == b"=" { match parse_mode { - ParseMode::Type | ParseMode::Arg => { + ParseMode::Arg | ParseMode::AfterType if last_token => working_set.error( + ParseError::Expected("default value", Span::new(span.end, span.end)), + ), + ParseMode::Arg | ParseMode::AfterType => { parse_mode = ParseMode::DefaultValue; } + ParseMode::Type => { + working_set.error(ParseError::Expected("type", span)); + } ParseMode::AfterCommaArg => { working_set.error(ParseError::Expected("parameter or flag", span)); } @@ -3467,7 +3478,9 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> // The , symbol separates params only else if contents == b"," { match parse_mode { - ParseMode::Arg => parse_mode = ParseMode::AfterCommaArg, + ParseMode::Arg | ParseMode::AfterType => { + parse_mode = ParseMode::AfterCommaArg + } ParseMode::AfterCommaArg => { working_set.error(ParseError::Expected("parameter or flag", span)); } @@ -3480,7 +3493,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> } } else { match parse_mode { - ParseMode::Arg | ParseMode::AfterCommaArg => { + ParseMode::Arg | ParseMode::AfterCommaArg | ParseMode::AfterType => { // Long flag with optional short form following with no whitespace, e.g. --output, --age(-a) if contents.starts_with(b"--") && contents.len() > 2 { // Split the long flag from the short flag with the ( character as delimiter. @@ -3790,7 +3803,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> } } } - parse_mode = ParseMode::Arg; + parse_mode = ParseMode::AfterType; } ParseMode::DefaultValue => { if let Some(last) = args.last_mut() { From f63f8cb15406c9f3f94e271f71767444763aa9a8 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:03:21 -0500 Subject: [PATCH 02/43] Add utouch command from uutils/coreutils (#11817) Part of https://github.com/nushell/nushell/issues/11549 # Description This PR adds a `utouch` command that uses the `touch` command from https://github.com/uutils/coreutils. Eventually, `utouch` may be able to replace `touch`. The conflicts in Cargo.lock and Cargo.toml are because I'm using the uutils/coreutils main rather than the latest release, since the changes that expose `uu_touch`'s internal functionality aren't available in the latest release. # User-Facing Changes Users will have access to a new `utouch` command with the following flags: todo # Tests + Formatting # After Submitting --- Cargo.lock | 26 + Cargo.toml | 1 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/utouch.rs | 268 ++++++++ crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/utouch.rs | 740 +++++++++++++++++++++ 8 files changed, 1040 insertions(+) create mode 100644 crates/nu-command/src/filesystem/utouch.rs create mode 100644 crates/nu-command/tests/commands/utouch.rs diff --git a/Cargo.lock b/Cargo.lock index 2754f5bd23..884f1993d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3229,6 +3229,7 @@ dependencies = [ "uu_mkdir", "uu_mktemp", "uu_mv", + "uu_touch", "uu_uname", "uu_whoami", "uucore", @@ -4081,6 +4082,17 @@ dependencies = [ "regex", ] +[[package]] +name = "parse_datetime" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0" +dependencies = [ + "chrono", + "nom", + "regex", +] + [[package]] name = "paste" version = "1.0.15" @@ -6745,6 +6757,20 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_touch" +version = "0.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55476bec11d5b70c578233a2e94f685058e0d65fc5d66c7ed465877c15124c7c" +dependencies = [ + "chrono", + "clap", + "filetime", + "parse_datetime", + "uucore", + "windows-sys 0.59.0", +] + [[package]] name = "uu_uname" version = "0.0.27" diff --git a/Cargo.toml b/Cargo.toml index a976aeec17..0297f5fdb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,6 +169,7 @@ uu_cp = "0.0.27" uu_mkdir = "0.0.27" uu_mktemp = "0.0.27" uu_mv = "0.0.27" +uu_touch = "0.0.28" uu_whoami = "0.0.27" uu_uname = "0.0.27" uucore = "0.0.27" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index e9c3b9a784..c37954e9c1 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -96,6 +96,7 @@ uu_cp = { workspace = true } uu_mkdir = { workspace = true } uu_mktemp = { workspace = true } uu_mv = { workspace = true } +uu_touch = { workspace = true } uu_uname = { workspace = true } uu_whoami = { workspace = true } uuid = { workspace = true, features = ["v4"] } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index ac6fb46631..cbbee717d4 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -230,6 +230,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Rm, Save, Touch, + UTouch, Glob, Watch, }; diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index acfa54fee3..089e899dda 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -12,6 +12,7 @@ mod ucp; mod umkdir; mod umv; mod util; +mod utouch; mod watch; pub use self::open::Open; @@ -27,4 +28,5 @@ pub use touch::Touch; pub use ucp::UCp; pub use umkdir::UMkdir; pub use umv::UMv; +pub use utouch::UTouch; pub use watch::Watch; diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs new file mode 100644 index 0000000000..f32364b28a --- /dev/null +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -0,0 +1,268 @@ +use std::io::ErrorKind; +use std::path::PathBuf; + +use chrono::{DateTime, FixedOffset}; +use filetime::FileTime; + +use nu_engine::CallExt; +use nu_path::expand_path_with; +use nu_protocol::engine::{Call, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, NuGlob, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, +}; +use uu_touch::error::TouchError; +use uu_touch::{ChangeTimes, InputFile, Options, Source}; + +use super::util::get_rest_for_glob_pattern; + +#[derive(Clone)] +pub struct UTouch; + +impl Command for UTouch { + fn name(&self) -> &str { + "utouch" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["create", "file"] + } + + fn signature(&self) -> Signature { + Signature::build("utouch") + .input_output_types(vec![ (Type::Nothing, Type::Nothing) ]) + .rest( + "files", + SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Filepath]), + "The file(s) to create. '-' is used to represent stdout." + ) + .named( + "reference", + SyntaxShape::Filepath, + "Use the access and modification times of the reference file/directory instead of the current time", + Some('r'), + ) + .named( + "timestamp", + SyntaxShape::DateTime, + "Use the given timestamp instead of the current time", + Some('t') + ) + .named( + "date", + SyntaxShape::String, + "Use the given time instead of the current time. This can be a full timestamp or it can be relative to either the current time or reference file time (if given). For more information, see https://www.gnu.org/software/coreutils/manual/html_node/touch-invocation.html", + Some('d') + ) + .switch( + "modified", + "Change only the modification time (if used with -a, access time is changed too)", + Some('m'), + ) + .switch( + "access", + "Change only the access time (if used with -m, modification time is changed too)", + Some('a'), + ) + .switch( + "no-create", + "Don't create the file if it doesn't exist", + Some('c'), + ) + .switch( + "no-deref", + "Affect each symbolic link instead of any referenced file (only for systems that can change the timestamps of a symlink). Ignored if touching stdout", + Some('s'), + ) + .category(Category::FileSystem) + } + + fn description(&self) -> &str { + "Creates one or more files." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let change_mtime: bool = call.has_flag(engine_state, stack, "modified")?; + let change_atime: bool = call.has_flag(engine_state, stack, "access")?; + let no_create: bool = call.has_flag(engine_state, stack, "no-create")?; + let no_deref: bool = call.has_flag(engine_state, stack, "no-dereference")?; + let file_globs: Vec> = + get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let cwd = engine_state.cwd(Some(stack))?; + + if file_globs.is_empty() { + return Err(ShellError::MissingParameter { + param_name: "requires file paths".to_string(), + span: call.head, + }); + } + + let (reference_file, reference_span) = if let Some(reference) = + call.get_flag::>(engine_state, stack, "reference")? + { + (Some(reference.item), Some(reference.span)) + } else { + (None, None) + }; + let (date_str, date_span) = + if let Some(date) = call.get_flag::>(engine_state, stack, "date")? { + (Some(date.item), Some(date.span)) + } else { + (None, None) + }; + let timestamp: Option>> = + call.get_flag(engine_state, stack, "timestamp")?; + + let source = if let Some(timestamp) = timestamp { + if let Some(reference_span) = reference_span { + return Err(ShellError::IncompatibleParameters { + left_message: "timestamp given".to_string(), + left_span: timestamp.span, + right_message: "reference given".to_string(), + right_span: reference_span, + }); + } + if let Some(date_span) = date_span { + return Err(ShellError::IncompatibleParameters { + left_message: "timestamp given".to_string(), + left_span: timestamp.span, + right_message: "date given".to_string(), + right_span: date_span, + }); + } + Source::Timestamp(FileTime::from_unix_time( + timestamp.item.timestamp(), + timestamp.item.timestamp_subsec_nanos(), + )) + } else if let Some(reference_file) = reference_file { + let reference_file = expand_path_with(reference_file, &cwd, true); + Source::Reference(reference_file) + } else { + Source::Now + }; + + let change_times = if change_atime && !change_mtime { + ChangeTimes::AtimeOnly + } else if change_mtime && !change_atime { + ChangeTimes::MtimeOnly + } else { + ChangeTimes::Both + }; + + let mut input_files = Vec::new(); + for file_glob in &file_globs { + if file_glob.item.as_ref() == "-" { + input_files.push(InputFile::Stdout); + } else { + let path = + expand_path_with(file_glob.item.as_ref(), &cwd, file_glob.item.is_expand()); + input_files.push(InputFile::Path(path)); + } + } + + if let Err(err) = uu_touch::touch( + &input_files, + &Options { + no_create, + no_deref, + source, + date: date_str, + change_times, + strict: true, + }, + ) { + let nu_err = match err { + TouchError::TouchFileError { path, index, error } => ShellError::GenericError { + error: format!("Could not touch {}", path.display()), + msg: error.to_string(), + span: Some(file_globs[index].span), + help: None, + inner: Vec::new(), + }, + TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue { + msg: format!("Invalid date: {}", date), + val_span: date_span.expect("utouch should've been given a date"), + call_span: call.head, + }, + TouchError::ReferenceFileInaccessible(reference_path, io_err) => { + let span = + reference_span.expect("utouch should've been given a reference file"); + if io_err.kind() == ErrorKind::NotFound { + ShellError::FileNotFound { + span, + file: reference_path.display().to_string(), + } + } else { + ShellError::GenericError { + error: io_err.to_string(), + msg: format!("Failed to read metadata of {}", reference_path.display()), + span: Some(span), + help: None, + inner: Vec::new(), + } + } + } + _ => ShellError::GenericError { + error: err.to_string(), + msg: err.to_string(), + span: Some(call.head), + help: None, + inner: Vec::new(), + }, + }; + return Err(nu_err); + } + + Ok(PipelineData::empty()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Creates \"fixture.json\"", + example: "utouch fixture.json", + result: None, + }, + Example { + description: "Creates files a, b and c", + example: "utouch a b c", + result: None, + }, + Example { + description: r#"Changes the last modified time of "fixture.json" to today's date"#, + example: "utouch -m fixture.json", + result: None, + }, + Example { + description: "Changes the last accessed and modified times of files a, b and c to the current time but yesterday", + example: r#"utouch -d "yesterday" a b c"#, + result: None, + }, + Example { + description: r#"Changes the last modified time of files d and e to "fixture.json"'s last modified time"#, + example: r#"utouch -m -r fixture.json d e"#, + result: None, + }, + Example { + description: r#"Changes the last accessed time of "fixture.json" to a datetime"#, + example: r#"utouch -a -t 2019-08-24T12:30:30 fixture.json"#, + result: None, + }, + Example { + description: r#"Change the last accessed and modified times of stdout"#, + example: r#"utouch -"#, + result: None, + }, + Example { + description: r#"Changes the last accessed and modified times of file a to 1 month before "fixture.json"'s last modified time"#, + example: r#"utouch -r fixture.json -d "-1 month" a"#, + result: None, + }, + ] + } +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 63911ebfbc..678b8d8896 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -127,6 +127,7 @@ mod update; mod upsert; mod url; mod use_; +mod utouch; mod where_; mod which; mod while_; diff --git a/crates/nu-command/tests/commands/utouch.rs b/crates/nu-command/tests/commands/utouch.rs new file mode 100644 index 0000000000..062ec7ddfc --- /dev/null +++ b/crates/nu-command/tests/commands/utouch.rs @@ -0,0 +1,740 @@ +use chrono::{DateTime, Days, Local, TimeDelta, Utc}; +use filetime::FileTime; +use nu_test_support::fs::{files_exist_at, Stub}; +use nu_test_support::nu; +use nu_test_support::playground::{Dirs, Playground}; +use std::path::Path; + +// Use 1 instead of 0 because 0 has a special meaning in Windows +const TIME_ONE: FileTime = FileTime::from_unix_time(1, 0); + +fn file_times(file: impl AsRef) -> (FileTime, FileTime) { + ( + file.as_ref().metadata().unwrap().accessed().unwrap().into(), + file.as_ref().metadata().unwrap().modified().unwrap().into(), + ) +} + +fn symlink_times(path: &nu_path::AbsolutePath) -> (filetime::FileTime, filetime::FileTime) { + let metadata = path.symlink_metadata().unwrap(); + + ( + filetime::FileTime::from_system_time(metadata.accessed().unwrap()), + filetime::FileTime::from_system_time(metadata.modified().unwrap()), + ) +} + +// From https://github.com/nushell/nushell/pull/14214 +fn setup_symlink_fs(dirs: &Dirs, sandbox: &mut Playground<'_>) { + sandbox.mkdir("d"); + sandbox.with_files(&[Stub::EmptyFile("f"), Stub::EmptyFile("d/f")]); + sandbox.symlink("f", "fs"); + sandbox.symlink("d", "ds"); + sandbox.symlink("d/f", "fds"); + + // sandbox.symlink does not handle symlinks to missing files well. It panics + // But they are useful, and they should be tested. + #[cfg(unix)] + { + std::os::unix::fs::symlink(dirs.test().join("m"), dirs.test().join("fms")).unwrap(); + } + + #[cfg(windows)] + { + std::os::windows::fs::symlink_file(dirs.test().join("m"), dirs.test().join("fms")).unwrap(); + } + + // Change the file times to a known "old" value for comparison + filetime::set_symlink_file_times(dirs.test().join("f"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("d"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("d/f"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("ds"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("fs"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("fds"), TIME_ONE, TIME_ONE).unwrap(); + filetime::set_symlink_file_times(dirs.test().join("fms"), TIME_ONE, TIME_ONE).unwrap(); +} + +#[test] +fn creates_a_file_when_it_doesnt_exist() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch i_will_be_created.txt" + ); + + let path = dirs.test().join("i_will_be_created.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn creates_two_files() { + Playground::setup("create_test_2", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch a b" + ); + + let path = dirs.test().join("a"); + assert!(path.exists()); + + let path2 = dirs.test().join("b"); + assert!(path2.exists()); + }) +} + +#[test] +fn change_modified_time_of_file_to_today() { + Playground::setup("change_time_test_9", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); + let path = dirs.test().join("file.txt"); + + // Set file.txt's times to the past before the test to make sure `utouch` actually changes the mtime to today + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -m file.txt" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + + // Check that atime remains unchanged + assert_eq!( + TIME_ONE, + FileTime::from_system_time(metadata.accessed().unwrap()) + ); + }) +} + +#[test] +fn change_access_time_of_file_to_today() { + Playground::setup("change_time_test_18", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); + let path = dirs.test().join("file.txt"); + + // Set file.txt's times to the past before the test to make sure `utouch` actually changes the atime to today + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -a file.txt" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, atime_day); + + // Check that mtime remains unchanged + assert_eq!( + TIME_ONE, + FileTime::from_system_time(metadata.modified().unwrap()) + ); + }) +} + +#[test] +fn change_modified_and_access_time_of_file_to_today() { + Playground::setup("change_time_test_27", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); + let path = dirs.test().join("file.txt"); + + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -a -m file.txt" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + assert_eq!(today, atime_day); + }) +} + +#[test] +fn not_create_file_if_it_not_exists() { + Playground::setup("change_time_test_28", |dirs, _sandbox| { + let outcome = nu!( + cwd: dirs.test(), + "utouch -c file.txt" + ); + + let path = dirs.test().join("file.txt"); + + assert!(!path.exists()); + + // If --no-create is improperly handled `utouch` may error when trying to change the times of a nonexistent file + assert!(outcome.status.success()) + }) +} + +#[test] +fn change_file_times_if_exists_with_no_create() { + Playground::setup( + "change_file_times_if_exists_with_no_create", + |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); + let path = dirs.test().join("file.txt"); + + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -c file.txt" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + assert_eq!(today, atime_day); + }, + ) +} + +#[test] +fn creates_file_three_dots() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch file..." + ); + + let path = dirs.test().join("file..."); + assert!(path.exists()); + }) +} + +#[test] +fn creates_file_four_dots() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch file...." + ); + + let path = dirs.test().join("file...."); + assert!(path.exists()); + }) +} + +#[test] +fn creates_file_four_dots_quotation_marks() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch 'file....'" + ); + + let path = dirs.test().join("file...."); + assert!(path.exists()); + }) +} + +#[test] +fn change_file_times_to_reference_file() { + Playground::setup("change_dir_times_to_reference_dir", |dirs, sandbox| { + sandbox.with_files(&[ + Stub::EmptyFile("reference_file"), + Stub::EmptyFile("target_file"), + ]); + + let reference = dirs.test().join("reference_file"); + let target = dirs.test().join("target_file"); + + // Change the times for reference + filetime::set_file_times(&reference, FileTime::from_unix_time(1337, 0), TIME_ONE).unwrap(); + + // target should have today's date since it was just created, but reference should be different + assert_ne!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_ne!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + + nu!( + cwd: dirs.test(), + "utouch -r reference_file target_file" + ); + + assert_eq!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_eq!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + }) +} + +#[test] +fn change_file_mtime_to_reference() { + Playground::setup("change_file_mtime_to_reference", |dirs, sandbox| { + sandbox.with_files(&[ + Stub::EmptyFile("reference_file"), + Stub::EmptyFile("target_file"), + ]); + + let reference = dirs.test().join("reference_file"); + let target = dirs.test().join("target_file"); + + // Change the times for reference + filetime::set_file_times(&reference, TIME_ONE, FileTime::from_unix_time(1337, 0)).unwrap(); + + // target should have today's date since it was just created, but reference should be different + assert_ne!(file_times(&reference), file_times(&target)); + + // Save target's current atime to make sure it is preserved + let target_original_atime = target.metadata().unwrap().accessed().unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -mr reference_file target_file" + ); + + assert_eq!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + assert_ne!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_eq!( + target_original_atime, + target.metadata().unwrap().accessed().unwrap() + ); + }) +} + +// TODO when https://github.com/uutils/coreutils/issues/6629 is fixed, +// unignore this test +#[test] +#[ignore] +fn change_file_times_to_reference_file_with_date() { + Playground::setup( + "change_file_times_to_reference_file_with_date", + |dirs, sandbox| { + sandbox.with_files(&[ + Stub::EmptyFile("reference_file"), + Stub::EmptyFile("target_file"), + ]); + + let reference = dirs.test().join("reference_file"); + let target = dirs.test().join("target_file"); + + let now = Utc::now(); + + let ref_atime = now; + let ref_mtime = now.checked_sub_days(Days::new(5)).unwrap(); + + // Change the times for reference + filetime::set_file_times( + reference, + FileTime::from_unix_time(ref_atime.timestamp(), ref_atime.timestamp_subsec_nanos()), + FileTime::from_unix_time(ref_mtime.timestamp(), ref_mtime.timestamp_subsec_nanos()), + ) + .unwrap(); + + nu!( + cwd: dirs.test(), + r#"utouch -r reference_file -d "yesterday" target_file"# + ); + + let (got_atime, got_mtime) = file_times(target); + let got = ( + DateTime::from_timestamp(got_atime.seconds(), got_atime.nanoseconds()).unwrap(), + DateTime::from_timestamp(got_mtime.seconds(), got_mtime.nanoseconds()).unwrap(), + ); + assert_eq!( + ( + now.checked_sub_days(Days::new(1)).unwrap(), + now.checked_sub_days(Days::new(6)).unwrap() + ), + got + ); + }, + ) +} + +#[test] +fn change_file_times_to_timestamp() { + Playground::setup("change_file_times_to_timestamp", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("target_file")]); + + let target = dirs.test().join("target_file"); + let timestamp = DateTime::from_timestamp(TIME_ONE.unix_seconds(), TIME_ONE.nanoseconds()) + .unwrap() + .to_rfc3339(); + + nu!(cwd: dirs.test(), format!("utouch --timestamp {} target_file", timestamp)); + + assert_eq!((TIME_ONE, TIME_ONE), file_times(target)); + }) +} + +#[test] +fn change_modified_time_of_dir_to_today() { + Playground::setup("change_dir_mtime", |dirs, sandbox| { + sandbox.mkdir("test_dir"); + let path = dirs.test().join("test_dir"); + + filetime::set_file_mtime(&path, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -m test_dir" + ); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = + DateTime::::from(path.metadata().unwrap().modified().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + }) +} + +#[test] +fn change_access_time_of_dir_to_today() { + Playground::setup("change_dir_atime", |dirs, sandbox| { + sandbox.mkdir("test_dir"); + let path = dirs.test().join("test_dir"); + + filetime::set_file_atime(&path, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -a test_dir" + ); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let atime_day = + DateTime::::from(path.metadata().unwrap().accessed().unwrap()).date_naive(); + + assert_eq!(today, atime_day); + }) +} + +#[test] +fn change_modified_and_access_time_of_dir_to_today() { + Playground::setup("change_dir_times", |dirs, sandbox| { + sandbox.mkdir("test_dir"); + let path = dirs.test().join("test_dir"); + + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -a -m test_dir" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + assert_eq!(today, atime_day); + }) +} + +// TODO when https://github.com/uutils/coreutils/issues/6629 is fixed, +// unignore this test +#[test] +#[ignore] +fn change_file_times_to_date() { + Playground::setup("change_file_times_to_date", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("target_file")]); + + let expected = Utc::now().checked_sub_signed(TimeDelta::hours(2)).unwrap(); + nu!(cwd: dirs.test(), "utouch -d '-2 hours' target_file"); + + let (got_atime, got_mtime) = file_times(dirs.test().join("target_file")); + let got_atime = + DateTime::from_timestamp(got_atime.seconds(), got_atime.nanoseconds()).unwrap(); + let got_mtime = + DateTime::from_timestamp(got_mtime.seconds(), got_mtime.nanoseconds()).unwrap(); + let threshold = TimeDelta::minutes(1); + assert!( + got_atime.signed_duration_since(expected).lt(&threshold) + && got_mtime.signed_duration_since(expected).lt(&threshold), + "Expected: {}. Got: atime={}, mtime={}", + expected, + got_atime, + got_mtime + ); + assert!(got_mtime.signed_duration_since(expected).lt(&threshold)); + }) +} + +#[test] +fn change_dir_three_dots_times() { + Playground::setup("change_dir_three_dots_times", |dirs, sandbox| { + sandbox.mkdir("test_dir..."); + let path = dirs.test().join("test_dir..."); + + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch test_dir..." + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + assert_eq!(today, atime_day); + }) +} + +#[test] +fn change_dir_times_to_reference_dir() { + Playground::setup("change_dir_times_to_reference_dir", |dirs, sandbox| { + sandbox.mkdir("reference_dir"); + sandbox.mkdir("target_dir"); + + let reference = dirs.test().join("reference_dir"); + let target = dirs.test().join("target_dir"); + + // Change the times for reference + filetime::set_file_times(&reference, FileTime::from_unix_time(1337, 0), TIME_ONE).unwrap(); + + // target should have today's date since it was just created, but reference should be different + assert_ne!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_ne!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + + nu!( + cwd: dirs.test(), + "utouch -r reference_dir target_dir" + ); + + assert_eq!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_eq!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + }) +} + +#[test] +fn change_dir_atime_to_reference() { + Playground::setup("change_dir_atime_to_reference", |dirs, sandbox| { + sandbox.mkdir("reference_dir"); + sandbox.mkdir("target_dir"); + + let reference = dirs.test().join("reference_dir"); + let target = dirs.test().join("target_dir"); + + // Change the times for reference + filetime::set_file_times(&reference, FileTime::from_unix_time(1337, 0), TIME_ONE).unwrap(); + + // target should have today's date since it was just created, but reference should be different + assert_ne!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_ne!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + + // Save target's current mtime to make sure it is preserved + let target_original_mtime = target.metadata().unwrap().modified().unwrap(); + + nu!( + cwd: dirs.test(), + "utouch -ar reference_dir target_dir" + ); + + assert_eq!( + reference.metadata().unwrap().accessed().unwrap(), + target.metadata().unwrap().accessed().unwrap() + ); + assert_ne!( + reference.metadata().unwrap().modified().unwrap(), + target.metadata().unwrap().modified().unwrap() + ); + assert_eq!( + target_original_mtime, + target.metadata().unwrap().modified().unwrap() + ); + }) +} + +#[test] +fn create_a_file_with_tilde() { + Playground::setup("utouch with tilde", |dirs, _| { + let actual = nu!(cwd: dirs.test(), "utouch '~tilde'"); + assert!(actual.err.is_empty()); + assert!(files_exist_at(&[Path::new("~tilde")], dirs.test())); + + // pass variable + let actual = nu!(cwd: dirs.test(), "let f = '~tilde2'; utouch $f"); + assert!(actual.err.is_empty()); + assert!(files_exist_at(&[Path::new("~tilde2")], dirs.test())); + }) +} + +#[test] +fn respects_cwd() { + Playground::setup("utouch_respects_cwd", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "mkdir 'dir'; cd 'dir'; utouch 'i_will_be_created.txt'" + ); + + let path = dirs.test().join("dir/i_will_be_created.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn reference_respects_cwd() { + Playground::setup("utouch_reference_respects_cwd", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "mkdir 'dir'; cd 'dir'; utouch 'ref.txt'; utouch --reference 'ref.txt' 'foo.txt'" + ); + + let path = dirs.test().join("dir/foo.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn recognizes_stdout() { + Playground::setup("utouch_recognizes_stdout", |dirs, _sandbox| { + nu!(cwd: dirs.test(), "utouch -"); + assert!(!dirs.test().join("-").exists()); + }) +} + +#[test] +fn follow_symlinks() { + Playground::setup("touch_follows_symlinks", |dirs, sandbox| { + setup_symlink_fs(&dirs, sandbox); + + let missing = dirs.test().join("m"); + assert!(!missing.exists()); + + nu!( + cwd: dirs.test(), + " + touch fds + touch ds + touch fs + touch fms + " + ); + + // We created the missing symlink target + assert!(missing.exists()); + + // The timestamps for files and directories were changed from TIME_ONE + let file_times = symlink_times(&dirs.test().join("f")); + let dir_times = symlink_times(&dirs.test().join("d")); + let dir_file_times = symlink_times(&dirs.test().join("d/f")); + + assert_ne!(file_times, (TIME_ONE, TIME_ONE)); + assert_ne!(dir_times, (TIME_ONE, TIME_ONE)); + assert_ne!(dir_file_times, (TIME_ONE, TIME_ONE)); + + // For symlinks, they remain (mostly) the same + // We can't test accessed times, since to reach the target file, the symlink must be accessed! + let file_symlink_times = symlink_times(&dirs.test().join("fs")); + let dir_symlink_times = symlink_times(&dirs.test().join("ds")); + let dir_file_symlink_times = symlink_times(&dirs.test().join("fds")); + let file_missing_symlink_times = symlink_times(&dirs.test().join("fms")); + + assert_eq!(file_symlink_times.1, TIME_ONE); + assert_eq!(dir_symlink_times.1, TIME_ONE); + assert_eq!(dir_file_symlink_times.1, TIME_ONE); + assert_eq!(file_missing_symlink_times.1, TIME_ONE); + }) +} + +#[test] +fn no_follow_symlinks() { + Playground::setup("touch_touches_symlinks", |dirs, sandbox| { + setup_symlink_fs(&dirs, sandbox); + + let missing = dirs.test().join("m"); + assert!(!missing.exists()); + + nu!( + cwd: dirs.test(), + " + touch fds -s + touch ds -s + touch fs -s + touch fms -s + " + ); + + // We did not create the missing symlink target + assert!(!missing.exists()); + + // The timestamps for files and directories remain the same + let file_times = symlink_times(&dirs.test().join("f")); + let dir_times = symlink_times(&dirs.test().join("d")); + let dir_file_times = symlink_times(&dirs.test().join("d/f")); + + assert_eq!(file_times, (TIME_ONE, TIME_ONE)); + assert_eq!(dir_times, (TIME_ONE, TIME_ONE)); + assert_eq!(dir_file_times, (TIME_ONE, TIME_ONE)); + + // For symlinks, everything changed. (except their targets, and paths, and personality) + let file_symlink_times = symlink_times(&dirs.test().join("fs")); + let dir_symlink_times = symlink_times(&dirs.test().join("ds")); + let dir_file_symlink_times = symlink_times(&dirs.test().join("fds")); + let file_missing_symlink_times = symlink_times(&dirs.test().join("fms")); + + assert_ne!(file_symlink_times, (TIME_ONE, TIME_ONE)); + assert_ne!(dir_symlink_times, (TIME_ONE, TIME_ONE)); + assert_ne!(dir_file_symlink_times, (TIME_ONE, TIME_ONE)); + assert_ne!(file_missing_symlink_times, (TIME_ONE, TIME_ONE)); + }) +} From 13ce9e4f64ba324e9cd62fc3780951ab5683c205 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:31:36 -0600 Subject: [PATCH 03/43] update uutils crates (#14371) # Description This PR updates the uutils/coreutils crates to the latest version. I hard-coded debug to false, a new uu_mv parameter. It may be interesting to add that but I just wanted to get all the uu crates on the same version. I had to update the tests because --no-clobber works but doesn't say anything when it's not clobbering and previously we were checking for an error message. # User-Facing Changes # Tests + Formatting # After Submitting --- Cargo.lock | 34 +++++++++---------- Cargo.toml | 14 ++++---- crates/nu-command/src/filesystem/umv.rs | 1 + crates/nu-command/tests/commands/move_/umv.rs | 9 +++-- crates/nu-command/tests/commands/ucp.rs | 11 +++--- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 884f1993d9..e6cef1232b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6709,9 +6709,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uu_cp" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb99d355ccb02e8c514e4a1d93e4aa4eedea9837de24635dfd24c165971444e" +checksum = "e0eff79f5eacf6bb88c9afc19f3cec2ab14ad31317be1369100658b46d41e410" dependencies = [ "clap", "filetime", @@ -6725,9 +6725,9 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219588fbc146f18188781208ac4034616c51cf151677b4e1f9caf63ca8a7f2cf" +checksum = "feba7cf875eecbb746b1c5a5a8a031ab3a00e5f44f5441643a06b78577780d3a" dependencies = [ "clap", "uucore", @@ -6735,9 +6735,9 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e79ad2c5911908fce23a6069c52ca82e1997e2ed4bf6abf2d867c79c3dc73f" +checksum = "1a9cfd389f60e667c5ee6659beaad50bada7e710d76082c7d77ab91e04307c8f" dependencies = [ "clap", "rand", @@ -6747,9 +6747,9 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd57c8d02f8a99ed56ed9f6fddab403ee0e2bf9e8f3a5ca8f0f9e4d6e3e392a0" +checksum = "bf932231fccdf108f75443bab0ce17acfe49b5825d731b8a358251833be7da20" dependencies = [ "clap", "fs_extra", @@ -6773,9 +6773,9 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1ca90f9b292bccaad0de70e6feccac5182c6713a5e1ca72d97bf3555b608b4" +checksum = "182b4071a2e6f7288cbbc1b1ff05c74e9dc7527b4735583d9e3cd92802b06910" dependencies = [ "clap", "platform-info", @@ -6784,27 +6784,27 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7c52e42e0425710461700adc1063f468f2ba8a8ff83ee69ba661095ab7b77a" +checksum = "5d15200414428c65f95d0b1d1226fc84f74ae80376bfe59959d93ddf57f944f5" dependencies = [ "clap", "libc", "uucore", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "uucore" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b54aad02cf7e96f5fafabb6b836efa73eef934783b17530095a29ffd4fdc154" +checksum = "04ea43050c46912575654c5181f4135529e8d4003fca80803af10cdef3ca6412" dependencies = [ "clap", "dunce", "glob", "libc", - "nix 0.28.0", + "nix 0.29.0", "number_prefix", "once_cell", "os_display", @@ -6812,7 +6812,7 @@ dependencies = [ "walkdir", "wild", "winapi-util", - "windows-sys 0.48.0", + "windows-sys 0.59.0", "xattr", ] diff --git a/Cargo.toml b/Cargo.toml index 0297f5fdb5..0b796573c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,14 +165,14 @@ unicode-segmentation = "1.12" unicode-width = "0.1" ureq = { version = "2.10", default-features = false } url = "2.2" -uu_cp = "0.0.27" -uu_mkdir = "0.0.27" -uu_mktemp = "0.0.27" -uu_mv = "0.0.27" +uu_cp = "0.0.28" +uu_mkdir = "0.0.28" +uu_mktemp = "0.0.28" +uu_mv = "0.0.28" uu_touch = "0.0.28" -uu_whoami = "0.0.27" -uu_uname = "0.0.27" -uucore = "0.0.27" +uu_whoami = "0.0.28" +uu_uname = "0.0.28" +uucore = "0.0.28" uuid = "1.11.0" v_htmlescape = "0.15.0" wax = "0.6" diff --git a/crates/nu-command/src/filesystem/umv.rs b/crates/nu-command/src/filesystem/umv.rs index 0f49a7d97d..929fc7bcbb 100644 --- a/crates/nu-command/src/filesystem/umv.rs +++ b/crates/nu-command/src/filesystem/umv.rs @@ -188,6 +188,7 @@ impl Command for UMv { target_dir: None, no_target_dir: false, strip_slashes: false, + debug: false, }; if let Err(error) = uu_mv::mv(&files, &options) { return Err(ShellError::GenericError { diff --git a/crates/nu-command/tests/commands/move_/umv.rs b/crates/nu-command/tests/commands/move_/umv.rs index b193d5b7cb..98e2a753bf 100644 --- a/crates/nu-command/tests/commands/move_/umv.rs +++ b/crates/nu-command/tests/commands/move_/umv.rs @@ -513,13 +513,18 @@ fn test_mv_no_clobber() { sandbox.with_files(&[EmptyFile(file_a)]); sandbox.with_files(&[EmptyFile(file_b)]); - let actual = nu!( + let _ = nu!( cwd: dirs.test(), "mv -n {} {}", file_a, file_b, ); - assert!(actual.err.contains("not replacing")); + + let file_count = nu!( + cwd: dirs.test(), + "ls test_mv* | length | to nuon" + ); + assert_eq!(file_count.out, "2"); }) } diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index eb2363e68c..e3fa2ca931 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -841,14 +841,13 @@ fn test_cp_arg_no_clobber() { let target = dirs.fixtures.join("cp").join(TEST_HOW_ARE_YOU_SOURCE); let target_hash = get_file_hash(target.display()); - let actual = nu!( - cwd: dirs.root(), - "cp {} {} --no-clobber", - src.display(), - target.display() + let _ = nu!( + cwd: dirs.root(), + "cp {} {} --no-clobber", + src.display(), + target.display() ); let after_cp_hash = get_file_hash(target.display()); - assert!(actual.err.contains("not replacing")); // Check content was not clobbered assert_eq!(after_cp_hash, target_hash); }); From 6773dfce8d5326a4afed4bfdc97edb24713d24d1 Mon Sep 17 00:00:00 2001 From: Wind Date: Tue, 19 Nov 2024 07:14:12 +0800 Subject: [PATCH 04/43] add `--default` flag to input command (#14374) # Description Closes: #14248 # User-Facing Changes Added a `--default` flag to input command, and it also added an extra output to prompt: ``` > let x = input -d 18 "input your age" input your age (default: 18) > $x 18 > let x = input -d 18 > $x 18 ``` # Tests + Formatting I don't think it's easy to add a test for it :-( --- .../nu-command/src/platform/input/input_.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/platform/input/input_.rs b/crates/nu-command/src/platform/input/input_.rs index cf46b400c0..f792db992e 100644 --- a/crates/nu-command/src/platform/input/input_.rs +++ b/crates/nu-command/src/platform/input/input_.rs @@ -43,6 +43,12 @@ impl Command for Input { "number of characters to read; suppresses output", Some('n'), ) + .named( + "default", + SyntaxShape::String, + "default value if no input is provided", + Some('d'), + ) .switch("suppress-output", "don't print keystroke values", Some('s')) .category(Category::Platform) } @@ -72,8 +78,12 @@ impl Command for Input { }); } + let default_val: Option = call.get_flag(engine_state, stack, "default")?; if let Some(prompt) = &prompt { - print!("{prompt}"); + match &default_val { + None => print!("{prompt}"), + Some(val) => print!("{prompt} (default: {val})"), + } let _ = std::io::stdout().flush(); } @@ -149,7 +159,10 @@ impl Command for Input { if !suppress_output { std::io::stdout().write_all(b"\n")?; } - Ok(Value::string(buf, call.head).into_pipeline_data()) + match default_val { + Some(val) if buf.is_empty() => Ok(Value::string(val, call.head).into_pipeline_data()), + _ => Ok(Value::string(buf, call.head).into_pipeline_data()), + } } fn examples(&self) -> Vec { @@ -164,6 +177,11 @@ impl Command for Input { example: "let user_input = (input --numchar 2)", result: None, }, + Example { + description: "Get input from the user with default value, and assign to a variable", + example: "let user_input = (input --default 10)", + result: None, + }, ] } } From 6e84ba182e2b4351c29c94241eadb5498431a6f7 Mon Sep 17 00:00:00 2001 From: Michel Lind Date: Mon, 18 Nov 2024 18:26:31 -0600 Subject: [PATCH 05/43] Bump quick-xml to 0.37.0 (#14354) # Description Bump `quick-xml` to `0.37.0`. This came about rebasing `nushell` in Fedora, which now has `quick-xml` 0.36. There is one breaking change in 0.33 as far as `nu-command` is concerned, in that `Event::PI` is now a dedicated `BytesPI` type: https://github.com/tafia/quick-xml/blob/master/Changelog.md#misc-changes-5 I've tested compiling and testing locally with `0.33.0`, `0.36.0` and `0.37.0` - but let's future-proof by requiring `0.37.0`. # User-Facing Changes N/A # Tests + Formatting No additional tests required, existing tests pass # After Submitting N/A Signed-off-by: Michel Lind --- Cargo.lock | 1167 ++++++++++++++--------- Cargo.toml | 2 +- crates/nu-command/src/formats/to/xml.rs | 4 +- 3 files changed, 708 insertions(+), 465 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6cef1232b..2821dab891 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -88,9 +82,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "alphanumeric-sort" @@ -134,9 +128,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -149,49 +143,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arboard" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" dependencies = [ "clipboard-win", "log", @@ -258,9 +252,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -269,9 +263,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", @@ -280,9 +274,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -312,9 +306,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "avro-schema" @@ -332,17 +326,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -354,12 +348,6 @@ dependencies = [ "backtrace", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -368,16 +356,14 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" -version = "0.69.4" +version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -448,9 +434,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases 0.2.1", @@ -458,16 +444,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.87", - "syn_derive", ] [[package]] @@ -499,9 +484,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata", @@ -516,9 +501,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-unit" -version = "5.1.4" +version = "5.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" dependencies = [ "rust_decimal", "serde", @@ -555,18 +540,18 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", @@ -625,9 +610,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.14" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -678,7 +663,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" dependencies = [ - "base64 0.22.1", + "base64", "encoding_rs", ] @@ -773,9 +758,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -783,22 +768,22 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", - "terminal_size", + "terminal_size 0.4.0", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -808,9 +793,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clipboard-win" @@ -832,9 +817,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colorz" @@ -847,14 +832,14 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" dependencies = [ - "crossterm 0.27.0", + "crossterm 0.28.1", "strum", "strum_macros", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -894,7 +879,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.11", "windows-sys 0.52.0", ] @@ -920,18 +905,18 @@ dependencies = [ [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -956,9 +941,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1113,9 +1098,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -1144,9 +1129,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", @@ -1159,9 +1144,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.74+curl-8.9.0" +version = "0.4.78+curl-8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" +checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" dependencies = [ "cc", "libc", @@ -1218,7 +1203,7 @@ dependencies = [ "console", "fuzzy-matcher", "shell-words", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1264,6 +1249,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1372,9 +1368,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -1452,9 +1448,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "ethnum" @@ -1493,9 +1489,9 @@ checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fd-lock" @@ -1510,11 +1506,11 @@ dependencies = [ [[package]] name = "file-id" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6584280525fb2059cba3db2c04abf947a1a29a45ddae89f3870f8281704fafc9" +checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1528,9 +1524,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -1546,12 +1542,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1569,6 +1565,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1626,9 +1628,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1641,9 +1643,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1651,15 +1653,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1668,15 +1670,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1685,21 +1687,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1766,9 +1768,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" @@ -1875,9 +1877,14 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" @@ -1995,9 +2002,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2014,7 +2021,7 @@ dependencies = [ "chrono", "pest", "pest_derive", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2025,9 +2032,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -2045,9 +2052,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-util", @@ -2060,9 +2067,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2087,17 +2094,146 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7cab7543a8b7729a19e2c04309f902861293dcdae6558dfbeb634454d279f6" dependencies = [ - "thiserror", + "thiserror 1.0.69", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -2107,21 +2243,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", "serde", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -2144,15 +2280,6 @@ dependencies = [ "libc", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "interprocess" version = "2.2.1" @@ -2216,9 +2343,9 @@ checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" [[package]] name = "is_executable" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] @@ -2285,9 +2412,9 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2329,17 +2456,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lexical-core" -version = "0.8.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458" dependencies = [ "lexical-parse-float", "lexical-parse-integer", @@ -2350,9 +2471,9 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "0.8.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -2361,9 +2482,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "0.8.6" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61" dependencies = [ "lexical-util", "static_assertions", @@ -2371,18 +2492,18 @@ dependencies = [ [[package]] name = "lexical-util" -version = "0.8.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0" dependencies = [ "static_assertions", ] [[package]] name = "lexical-write-float" -version = "0.8.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809" dependencies = [ "lexical-util", "lexical-write-integer", @@ -2391,9 +2512,9 @@ dependencies = [ [[package]] name = "lexical-write-integer" -version = "0.8.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162" dependencies = [ "lexical-util", "static_assertions", @@ -2451,9 +2572,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" @@ -2467,9 +2588,9 @@ dependencies = [ [[package]] name = "libproc" -version = "0.14.8" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9ea4b75e1a81675429dafe43441df1caea70081e82246a8cccf514884a88bb" +checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" dependencies = [ "bindgen", "errno", @@ -2514,9 +2635,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -2539,6 +2660,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -2557,11 +2684,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -2575,9 +2702,9 @@ dependencies = [ [[package]] name = "lsp-server" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248f65b78f6db5d8e1b1604b4098a28b43d21a8eb1deeca22b1c421b276c7095" +checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9" dependencies = [ "crossbeam-channel", "log", @@ -2600,19 +2727,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -2709,13 +2835,13 @@ dependencies = [ "cfg-if", "miette-derive", "owo-colors", - "supports-color 3.0.0", + "supports-color 3.0.1", "supports-hyperlinks", "supports-unicode", - "terminal_size", + "terminal_size 0.3.0", "textwrap", - "thiserror", - "unicode-width", + "thiserror 1.0.69", + "unicode-width 0.1.11", ] [[package]] @@ -2760,15 +2886,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -3135,7 +3252,7 @@ name = "nu-command" version = "0.100.1" dependencies = [ "alphanumeric-sort", - "base64 0.22.1", + "base64", "bracoxide", "brotli", "byteorder", @@ -3197,7 +3314,7 @@ dependencies = [ "pretty_assertions", "print-positions", "procfs", - "quick-xml 0.32.0", + "quick-xml 0.37.0", "quickcheck", "quickcheck_macros", "rand", @@ -3216,13 +3333,13 @@ dependencies = [ "sysinfo 0.32.0", "tabled", "tempfile", - "terminal_size", + "terminal_size 0.3.0", "titlecase", "toml 0.8.19", "trash", "umask", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.11", "ureq", "url", "uu_cp", @@ -3237,7 +3354,7 @@ dependencies = [ "v_htmlescape", "wax", "which", - "windows", + "windows 0.56.0", "winreg", ] @@ -3261,7 +3378,7 @@ dependencies = [ "nu-path", "nu-protocol", "nu-utils", - "terminal_size", + "terminal_size 0.3.0", ] [[package]] @@ -3285,8 +3402,8 @@ dependencies = [ "nu-utils", "ratatui", "strip-ansi-escapes", - "terminal_size", - "unicode-width", + "terminal_size 0.3.0", + "unicode-width 0.1.11", ] [[package]] @@ -3368,7 +3485,7 @@ dependencies = [ "nu-protocol", "nu-utils", "serde", - "thiserror", + "thiserror 1.0.69", "typetag", ] @@ -3383,7 +3500,7 @@ dependencies = [ "rmp-serde", "serde", "serde_json", - "windows", + "windows 0.56.0", ] [[package]] @@ -3399,7 +3516,7 @@ dependencies = [ "nu-utils", "serde", "typetag", - "windows", + "windows 0.56.0", ] [[package]] @@ -3474,7 +3591,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror", + "thiserror 1.0.69", "typetag", "windows-sys 0.48.0", ] @@ -3504,7 +3621,7 @@ dependencies = [ "ntapi", "procfs", "sysinfo 0.32.0", - "windows", + "windows 0.56.0", ] [[package]] @@ -3518,7 +3635,7 @@ dependencies = [ "nu-protocol", "nu-utils", "tabled", - "terminal_size", + "terminal_size 0.3.0", ] [[package]] @@ -3526,7 +3643,7 @@ name = "nu-term-grid" version = "0.100.1" dependencies = [ "nu-utils", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -3887,9 +4004,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -3914,18 +4031,15 @@ checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575" [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "open" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" dependencies = [ "is-wsl", "libc", @@ -3934,9 +4048,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -3966,18 +4080,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -4008,7 +4122,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6229bad892b46b0dcfaaeb18ad0d2e56400f5aaea05b768bde96e73676cf75" dependencies = [ - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -4023,9 +4137,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" [[package]] name = "papergrid" @@ -4037,7 +4151,7 @@ dependencies = [ "ansitok", "bytecount", "fnv", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -4101,9 +4215,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" [[package]] name = "percent-encoding" @@ -4119,20 +4233,20 @@ checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -4140,9 +4254,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -4153,9 +4267,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -4245,9 +4359,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -4257,9 +4371,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -4278,9 +4392,9 @@ dependencies = [ [[package]] name = "platform-info" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff316b9c4642feda973c18f0decd6c8b0919d4722566f6e4337cce0dd88217" +checksum = "91077ffd05d058d70d79eefcd7d7f6aac34980860a7519960f7913b6563a8c3a" dependencies = [ "libc", "winapi", @@ -4292,7 +4406,7 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.22.1", + "base64", "indexmap", "quick-xml 0.32.0", "serde", @@ -4413,7 +4527,7 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "version_check", "xxhash-rust", ] @@ -4428,7 +4542,7 @@ dependencies = [ "polars-arrow-format", "regex", "simdutf8", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4567,7 +4681,7 @@ checksum = "5ac14a136d87bea798f3db51d5987556deb2293da34bfc8b105ebffa05f6e810" dependencies = [ "ahash 0.8.11", "argminmax", - "base64 0.22.1", + "base64", "bytemuck", "chrono", "chrono-tz 0.8.6", @@ -4603,7 +4717,7 @@ checksum = "491f5af321169259d5b1294c9fe8ed89faaeac34b4dec4abcedc0d1b3d11013a" dependencies = [ "ahash 0.8.11", "async-stream", - "base64 0.22.1", + "base64", "brotli", "bytemuck", "ethnum", @@ -4787,9 +4901,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -4841,9 +4955,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -4860,11 +4974,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] @@ -4892,9 +5006,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -4927,9 +5041,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] @@ -4967,7 +5081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5003,9 +5117,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.34.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" dependencies = [ "memchr", ] @@ -5110,14 +5233,14 @@ dependencies = [ "strum", "unicode-segmentation", "unicode-truncate", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] name = "raw-cpuid" -version = "11.1.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ "bitflags 2.6.0", ] @@ -5170,9 +5293,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -5185,7 +5308,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5206,9 +5329,9 @@ dependencies = [ "strip-ansi-escapes", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -5233,9 +5356,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -5245,9 +5368,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -5256,9 +5379,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" @@ -5277,16 +5400,16 @@ dependencies = [ [[package]] name = "rfc2047-decoder" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90a668c463c412c3118ae1883e18b53d812c349f5af7a06de3ba4bb0c17cc73" +checksum = "bc36545d1021456a751b573517cb52e8c339b2f662e6b2778ef629282678de29" dependencies = [ - "base64 0.21.7", + "base64", "charset", "chumsky", "memchr", "quoted_printable", - "thiserror", + "thiserror 2.0.3", ] [[package]] @@ -5501,9 +5624,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -5522,20 +5645,20 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.16" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb7ac86243095b70a7920639507b71d51a63390d1ba26c4f60a552fbb914a37" +checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5587,9 +5710,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" [[package]] name = "seahash" @@ -5612,9 +5735,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -5647,18 +5770,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -5667,9 +5790,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "indexmap", "itoa", @@ -5691,9 +5814,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5770,9 +5893,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca0e9bdc073d7173ba993fb7886477af5df75588b57afcb4b96f21911ab0bfa" +checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687" dependencies = [ "const_format", "is_debug", @@ -5824,9 +5947,9 @@ dependencies = [ [[package]] name = "simd-json" -version = "0.13.10" +version = "0.13.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570c430b3d902ea083097e853263ae782dfe40857d93db019a12356c8e8143fa" +checksum = "a0228a564470f81724e30996bbc2b171713b37b15254a6440c7e2d5449b95691" dependencies = [ "ahash 0.8.11", "getrandom", @@ -5842,9 +5965,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -5933,15 +6056,15 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a5daa25ea337c85ed954c0496e3bdd2c7308cc3b24cf7b50d04876654c579f" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys 0.36.1", + "windows-sys 0.59.0", ] [[package]] @@ -5952,9 +6075,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "str_indices" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" [[package]] name = "streaming-decompression" @@ -6052,9 +6175,9 @@ dependencies = [ [[package]] name = "supports-color" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" dependencies = [ "is_ci", ] @@ -6115,12 +6238,11 @@ dependencies = [ ] [[package]] -name = "syn_derive" -version = "0.1.8" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", "syn 2.0.87", @@ -6128,9 +6250,9 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" dependencies = [ "libc", ] @@ -6145,7 +6267,7 @@ dependencies = [ "libc", "memchr", "ntapi", - "windows", + "windows 0.57.0", ] [[package]] @@ -6159,7 +6281,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows", + "windows 0.57.0", ] [[package]] @@ -6191,7 +6313,7 @@ dependencies = [ "rand", "scroll", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6249,6 +6371,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -6263,23 +6395,43 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", @@ -6338,6 +6490,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -6366,9 +6528,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.3" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -6382,9 +6544,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -6415,7 +6577,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -6442,26 +6604,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -6497,17 +6648,16 @@ dependencies = [ "once_cell", "scopeguard", "urlencoding", - "windows", + "windows 0.56.0", ] [[package]] name = "tree_magic_mini" -version = "3.1.5" +version = "3.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a727cac55b41448315cc10427c069c618ac59bb6a4480283fcd811749bdc2" +checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" dependencies = [ "fnv", - "home", "memchr", "nom", "once_cell", @@ -6564,9 +6714,9 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "umask" @@ -6574,7 +6724,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec9a46c2549e35c054e0ffe281a3a6ec0007793db4df106604d37ed3f4d73d1c" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6583,12 +6733,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -6601,15 +6745,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-reverse" version = "1.0.9" @@ -6633,7 +6768,7 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools 0.13.0", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -6643,10 +6778,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] -name = "unicode-xid" -version = "0.2.5" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" @@ -6660,7 +6801,7 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ - "base64 0.22.1", + "base64", "encoding_rs", "flate2", "log", @@ -6673,9 +6814,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -6695,12 +6836,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -6931,9 +7084,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -6942,9 +7095,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -6957,9 +7110,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6967,9 +7120,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -6980,9 +7133,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wax" @@ -6995,15 +7148,15 @@ dependencies = [ "nom", "pori", "regex", - "thiserror", + "thiserror 1.0.69", "walkdir", ] [[package]] name = "wayland-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", @@ -7015,9 +7168,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", "rustix", @@ -7052,26 +7205,36 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.34.0", + "quick-xml 0.36.2", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", "pkg-config", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpage" version = "2.0.1" @@ -7154,6 +7317,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -7169,8 +7342,20 @@ version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.56.0", + "windows-interface 0.56.0", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", "windows-result", "windows-targets 0.52.6", ] @@ -7186,6 +7371,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "windows-interface" version = "0.56.0" @@ -7197,6 +7393,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -7206,19 +7413,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -7289,12 +7483,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7307,12 +7495,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -7331,12 +7513,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -7349,12 +7525,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -7379,12 +7549,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -7408,9 +7572,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -7453,7 +7617,7 @@ dependencies = [ "nix 0.28.0", "os_pipe", "tempfile", - "thiserror", + "thiserror 1.0.69", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -7461,6 +7625,18 @@ dependencies = [ "wayland-protocols-wlr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -7517,9 +7693,33 @@ checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] [[package]] name = "zerocopy" @@ -7542,6 +7742,49 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 0b796573c8..ad2e886ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,7 +129,7 @@ proc-macro-error = { version = "1.0", default-features = false } proc-macro2 = "1.0" procfs = "0.16.0" pwd = "1.3" -quick-xml = "0.32.0" +quick-xml = "0.37.0" quickcheck = "1.0" quickcheck_macros = "1.0" quote = "1.0" diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index 77708aa375..4476f654c2 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -4,7 +4,7 @@ use nu_engine::command_prelude::*; use quick_xml::{ escape, - events::{BytesEnd, BytesStart, BytesText, Event}, + events::{BytesEnd, BytesPI, BytesStart, BytesText, Event}, }; use std::{borrow::Cow, io::Cursor}; @@ -406,7 +406,7 @@ impl Job { let content_text = format!("{} {}", tag, content); // PI content must NOT be escaped // https://www.w3.org/TR/xml/#sec-pi - let pi_content = BytesText::from_escaped(content_text.as_str()); + let pi_content = BytesPI::new(content_text.as_str()); self.writer .write_event(Event::PI(pi_content)) From d69e13145040e25807aa9ee147d9de3fa53b5764 Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Tue, 19 Nov 2024 08:04:29 -0500 Subject: [PATCH 06/43] Rely on `display_output` hook for formatting values from evaluations (#14361) # Description I was reading through the documentation yesterday, when I stumbled upon [this section](https://www.nushell.sh/book/pipelines.html#behind-the-scenes) explaining how command output is formatted using the `table` command. I was surprised that this section didn't mention the `display_output` hook, so I took a look in the code and was shocked to discovered that the documentation was correct, and the `table` command _is_ automatically applied to printed pipelines. This auto-tabling has two ramifications for the `display_output` hook: 1. The `table` command is called on the output of a pipeline after the `display_output` has run, even if `display_output` contains the table command. This means each pipeline output is roughly equivalent to the following (using `ls` as an example): ```nushell ls | do $config.hooks.display_output | table ``` 2. If `display_output` returns structured data, it will _still_ be formatted through the table command. This PR removes the auto-table when the `display_output` hook is set. The auto-table made sense before `display_output` was introduced, but to me, it now seems like unnecessary "automagic" which can be accomplished using existing Nushell features. This means that you can now pull back the curtain a bit, and replace your `display_output` hook with an empty closure (`$env.config.hooks.display_output = {||}`, setting it to null retains the previous behavior) to see the values printed normally without the table formatting. I think this is a good thing, and makes it easier to understand Nushell fundamentals. It is important to note that this PR does not change how `print` and other commands (well, specifically only `watch`) print out values. They continue to use `table` with no arguments, so changing your config/`display_output` hook won't affect what `print`ing a value does. Rel: [Discord discussion](https://discord.com/channels/601130461678272522/615329862395101194/1307102690848931904) (cc @dcarosone) # User-Facing Changes Pipelines are no longer automatically formatted using the `table` command. Instead, the `display_output` hook is used to format pipeline output. Most users should see no impact, as the default `display_output` hook already uses the `table` command. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting Will update mentioned docs page to call out `display_output` hook. --- crates/nu-cli/src/eval_cmds.rs | 4 +- crates/nu-cli/src/eval_file.rs | 4 +- crates/nu-cli/src/print.rs | 10 +++- crates/nu-cli/src/util.rs | 57 ++++++++++--------- crates/nu-command/src/filesystem/watch.rs | 2 +- .../nu-protocol/src/pipeline/pipeline_data.rs | 9 ++- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index f0bebfc884..663185a4a2 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -9,6 +9,8 @@ use nu_protocol::{ }; use std::sync::Arc; +use crate::util::print_pipeline; + #[derive(Default)] pub struct EvaluateCommandsOpts { pub table_mode: Option, @@ -93,7 +95,7 @@ pub fn evaluate_commands( t_mode.coerce_str()?.parse().unwrap_or_default(); } - pipeline.print(engine_state, stack, no_newline, false)?; + print_pipeline(engine_state, stack, pipeline, no_newline)?; info!("evaluate {}:{}:{}", file!(), line!(), column!()); diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index df75f7e4f8..7636adc8d4 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -1,4 +1,4 @@ -use crate::util::eval_source; +use crate::util::{eval_source, print_pipeline}; use log::{info, trace}; use nu_engine::{convert_env_values, eval_block}; use nu_parser::parse; @@ -119,7 +119,7 @@ pub fn evaluate_file( }; // Print the pipeline output of the last command of the file. - pipeline.print(engine_state, stack, true, false)?; + print_pipeline(engine_state, stack, pipeline, true)?; // Invoke the main command with arguments. // Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace. diff --git a/crates/nu-cli/src/print.rs b/crates/nu-cli/src/print.rs index ffb0366242..40660ecf13 100644 --- a/crates/nu-cli/src/print.rs +++ b/crates/nu-cli/src/print.rs @@ -65,8 +65,12 @@ Since this command has no output, there is no point in piping it with other comm arg.into_pipeline_data() .print_raw(engine_state, no_newline, to_stderr)?; } else { - arg.into_pipeline_data() - .print(engine_state, stack, no_newline, to_stderr)?; + arg.into_pipeline_data().print_table( + engine_state, + stack, + no_newline, + to_stderr, + )?; } } } else if !input.is_nothing() { @@ -78,7 +82,7 @@ Since this command has no output, there is no point in piping it with other comm if raw { input.print_raw(engine_state, no_newline, to_stderr)?; } else { - input.print(engine_state, stack, no_newline, to_stderr)?; + input.print_table(engine_state, stack, no_newline, to_stderr)?; } } diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 67bc1ad8ef..3e69657857 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -201,6 +201,35 @@ fn gather_env_vars( } } +/// Print a pipeline with formatting applied based on display_output hook. +/// +/// This function should be preferred when printing values resulting from a completed evaluation. +/// For values printed as part of a command's execution, such as values printed by the `print` command, +/// the `PipelineData::print_table` function should be preferred instead as it is not config-dependent. +/// +/// `no_newline` controls if we need to attach newline character to output. +pub fn print_pipeline( + engine_state: &mut EngineState, + stack: &mut Stack, + pipeline: PipelineData, + no_newline: bool, +) -> Result<(), ShellError> { + if let Some(hook) = engine_state.get_config().hooks.display_output.clone() { + let pipeline = eval_hook( + engine_state, + stack, + Some(pipeline), + vec![], + &hook, + "display_output", + )?; + pipeline.print_raw(engine_state, no_newline, false) + } else { + // if display_output isn't set, we should still prefer to print with some formatting + pipeline.print_table(engine_state, stack, no_newline, false) + } +} + pub fn eval_source( engine_state: &mut EngineState, stack: &mut Stack, @@ -281,36 +310,12 @@ fn evaluate_source( eval_block::(engine_state, stack, &block, input) }?; - if let PipelineData::ByteStream(..) = pipeline { - // run the display hook on bytestreams too - run_display_hook(engine_state, stack, pipeline, false) - } else { - run_display_hook(engine_state, stack, pipeline, true) - }?; + let no_newline = matches!(&pipeline, &PipelineData::ByteStream(..)); + print_pipeline(engine_state, stack, pipeline, no_newline)?; Ok(false) } -fn run_display_hook( - engine_state: &mut EngineState, - stack: &mut Stack, - pipeline: PipelineData, - no_newline: bool, -) -> Result<(), ShellError> { - if let Some(hook) = engine_state.get_config().hooks.display_output.clone() { - let pipeline = eval_hook( - engine_state, - stack, - Some(pipeline), - vec![], - &hook, - "display_output", - )?; - pipeline.print(engine_state, stack, no_newline, false) - } else { - pipeline.print(engine_state, stack, no_newline, false) - } -} #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index 6f022acca8..dd21304173 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -194,7 +194,7 @@ impl Command for Watch { match result { Ok(val) => { - val.print(engine_state, stack, false, false)?; + val.print_table(engine_state, stack, false, false)?; } Err(err) => { let working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index 827f6e31a7..75449f547f 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -203,7 +203,7 @@ impl PipelineData { ) -> Result { match stack.pipe_stdout().unwrap_or(&OutDest::Inherit) { OutDest::Print => { - self.print(engine_state, stack, false, false)?; + self.print_table(engine_state, stack, false, false)?; Ok(Self::Empty) } OutDest::Pipe | OutDest::PipeSeparate => Ok(self), @@ -534,11 +534,14 @@ impl PipelineData { } } - /// Consume and print self data immediately. + /// Consume and print self data immediately, formatted using table command. + /// + /// This does not respect the display_output hook. If a value is being printed out by a command, + /// this function should be used. Otherwise, `nu_cli::util::print_pipeline` should be preferred. /// /// `no_newline` controls if we need to attach newline character to output. /// `to_stderr` controls if data is output to stderr, when the value is false, the data is output to stdout. - pub fn print( + pub fn print_table( self, engine_state: &EngineState, stack: &mut Stack, From 9cffbdb42ac90055754e36b23afc6368e34e2983 Mon Sep 17 00:00:00 2001 From: Wind Date: Tue, 19 Nov 2024 21:52:58 +0800 Subject: [PATCH 07/43] remove deprecated warnings (#14386) # Description While looking into nushell deprecated relative code, I found `str contains` have some warnings, but it should be removed. --- crates/nu-command/src/strings/str_/contains.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index de91768e76..d24fac6668 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -77,20 +77,6 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - if call.has_flag_const(working_set, "not")? { - nu_protocol::report_shell_error( - working_set.permanent(), - &ShellError::GenericError { - error: "Deprecated option".into(), - msg: "`str contains --not {string}` is deprecated and will be removed in 0.95." - .into(), - span: Some(call.head), - help: Some("Please use the `not` operator instead.".into()), - inner: vec![], - }, - ); - } - let cell_paths: Vec = call.rest_const(working_set, 1)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let args = Arguments { From b6ce907928d872957e3b47f95a9ccda80f1eed9c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 20 Nov 2024 00:31:28 +0300 Subject: [PATCH 08/43] =?UTF-8?q?nu-table/=20Do=20footer=5Finheritance=20b?= =?UTF-8?q?y=20accouting=20for=20rows=20rather=20then=20a=20f=E2=80=A6=20(?= =?UTF-8?q?#14380)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So it's my take on the comments in #14060 The change could be seen in this test. Looks like it works :) but I haven't done a lot of testing. https://github.com/zhiburt/nushell/blob/0b1af774157c182675e471e67c184f6080721794/crates/nu-command/tests/commands/table.rs#L3032-L3062 ```nushell $env.config.table.footer_inheritance = true; $env.config.footer_mode = 7; [[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80 ``` ```text ╭───┬──────┬───────────────────────╮ │ # │ a │ b │ ├───┼──────┼───────────────────────┤ │ 0 │ kv │ ╭───┬───────────────╮ │ │ │ │ │ │ ╭───┬───────╮ │ │ │ │ │ │ 0 │ │ # │ field │ │ │ │ │ │ │ │ ├───┼───────┤ │ │ │ │ │ │ │ │ 0 │ 0 │ │ │ │ │ │ │ │ │ 1 │ 1 │ │ │ │ │ │ │ │ │ 2 │ 2 │ │ │ │ │ │ │ │ │ 3 │ 3 │ │ │ │ │ │ │ │ │ 4 │ 4 │ │ │ │ │ │ │ │ │ 5 │ 5 │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │ │ ╰───┴───────────────╯ │ │ 1 │ data │ 0 │ │ 2 │ data │ 0 │ ├───┼──────┼───────────────────────┤ │ # │ a │ b │ ╰───┴──────┴───────────────────────╯ ``` Maybe it will also solve the issue you @fdncred encountered. close #14060 cc: @NotTheDr01ds --- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-command/tests/commands/table.rs | 120 ++++++++++++++++++++++ crates/nu-table/src/common.rs | 8 +- crates/nu-table/src/table.rs | 4 + crates/nu-table/src/types/expanded.rs | 57 +++++----- crates/nu-table/src/types/general.rs | 13 ++- crates/nu-table/src/types/mod.rs | 31 +----- 7 files changed, 170 insertions(+), 65 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index a9e7361a9e..50db949ced 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1088,7 +1088,7 @@ fn create_empty_placeholder( let data = vec![vec![cell]]; let mut table = NuTable::from(data); table.set_data_style(TextStyle::default().dimmed()); - let out = TableOutput::new(table, false, false, false); + let out = TableOutput::new(table, false, false, 1); let style_computer = &StyleComputer::from_config(engine_state, stack); let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default()); diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 96ae395e47..b9b80b9e87 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2941,3 +2941,123 @@ fn table_footer_inheritance() { assert_eq!(actual.out.match_indices("x2").count(), 1); assert_eq!(actual.out.match_indices("x3").count(), 1); } + +#[test] +fn table_footer_inheritance_kv_rows() { + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────┤\ + │ 0 │ kv │ ╭───┬───╮ │\ + │ │ │ │ 0 │ 0 │ │\ + │ │ │ │ 1 │ 1 │ │\ + │ │ │ │ 2 │ 2 │ │\ + │ │ │ │ 3 │ 3 │ │\ + │ │ │ │ 4 │ 4 │ │\ + │ │ │ ╰───┴───╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ╰───┴──────┴───────────╯" + ); + + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────┤\ + │ 0 │ kv │ ╭───┬───╮ │\ + │ │ │ │ 0 │ 0 │ │\ + │ │ │ │ 1 │ 1 │ │\ + │ │ │ │ 2 │ 2 │ │\ + │ │ │ │ 3 │ 3 │ │\ + │ │ │ │ 4 │ 4 │ │\ + │ │ │ │ 5 │ 5 │ │\ + │ │ │ ╰───┴───╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ├───┼──────┼───────────┤\ + │ # │ a │ b │\ + ╰───┴──────┴───────────╯" + ); +} + +#[test] +fn table_footer_inheritance_list_rows() { + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────────────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────────────────┤\ + │ 0 │ kv │ ╭───┬───────────────╮ │\ + │ │ │ │ │ ╭───┬───────╮ │ │\ + │ │ │ │ 0 │ │ # │ field │ │ │\ + │ │ │ │ │ ├───┼───────┤ │ │\ + │ │ │ │ │ │ 0 │ 0 │ │ │\ + │ │ │ │ │ │ 1 │ 1 │ │ │\ + │ │ │ │ │ │ 2 │ 2 │ │ │\ + │ │ │ │ │ │ 3 │ 3 │ │ │\ + │ │ │ │ │ │ 4 │ 4 │ │ │\ + │ │ │ │ │ ╰───┴───────╯ │ │\ + │ │ │ ╰───┴───────────────╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ╰───┴──────┴───────────────────────╯" + ); + + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────────────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────────────────┤\ + │ 0 │ kv │ ╭───┬───────────────╮ │\ + │ │ │ │ │ ╭───┬───────╮ │ │\ + │ │ │ │ 0 │ │ # │ field │ │ │\ + │ │ │ │ │ ├───┼───────┤ │ │\ + │ │ │ │ │ │ 0 │ 0 │ │ │\ + │ │ │ │ │ │ 1 │ 1 │ │ │\ + │ │ │ │ │ │ 2 │ 2 │ │ │\ + │ │ │ │ │ │ 3 │ 3 │ │ │\ + │ │ │ │ │ │ 4 │ 4 │ │ │\ + │ │ │ │ │ │ 5 │ 5 │ │ │\ + │ │ │ │ │ ╰───┴───────╯ │ │\ + │ │ │ ╰───┴───────────────╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ├───┼──────┼───────────────────────┤\ + │ # │ a │ b │\ + ╰───┴──────┴───────────────────────╯" + ); +} diff --git a/crates/nu-table/src/common.rs b/crates/nu-table/src/common.rs index d18e053a1c..13c8f84fbc 100644 --- a/crates/nu-table/src/common.rs +++ b/crates/nu-table/src/common.rs @@ -18,8 +18,12 @@ pub fn create_nu_table_config( expand: bool, mode: TableMode, ) -> NuTableConfig { - let with_footer = (config.table.footer_inheritance && out.with_footer) - || with_footer(config, out.with_header, out.table.count_rows()); + let mut count_rows = out.table.count_rows(); + if config.table.footer_inheritance { + count_rows = out.count_rows; + } + + let with_footer = with_footer(config, out.with_header, count_rows); NuTableConfig { theme: load_theme(mode), diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 971e5b1f91..bf75e14380 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -615,12 +615,15 @@ fn load_theme( if let Some(style) = sep_color { let color = convert_style(style); let color = ANSIBuf::from(color); + // todo: use .modify(Segment::all(), color) --> it has this optimization table.get_config_mut().set_border_color_default(color); } if !with_header { + // todo: remove and use theme.remove_horizontal_lines(); table.with(RemoveHorizontalLine); } else if with_footer { + // todo: remove and set it on theme rather then here... table.with(CopyFirstHorizontalLineAtLast); } } @@ -1257,6 +1260,7 @@ fn remove_row(recs: &mut NuRecords, row: usize) -> Vec { columns } +// todo; use Format? struct StripColorFromRow(usize); impl TableOption> for StripColorFromRow { diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index a0b6cd0ff2..d87af74522 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -5,7 +5,7 @@ use crate::{ NuText, StringResult, TableResult, INDEX_COLUMN_NAME, }, string_width, - types::{has_footer, has_index}, + types::has_index, NuTable, NuTableCell, TableOpts, TableOutput, }; use nu_color_config::{Alignment, StyleComputer, TextStyle}; @@ -63,22 +63,22 @@ struct Cfg<'a> { struct CellOutput { text: String, style: TextStyle, - is_big: bool, + size: usize, is_expanded: bool, } impl CellOutput { - fn new(text: String, style: TextStyle, is_big: bool, is_expanded: bool) -> Self { + fn new(text: String, style: TextStyle, size: usize, is_expanded: bool) -> Self { Self { text, style, - is_big, + size, is_expanded, } } - fn clean(text: String, is_big: bool, is_expanded: bool) -> Self { - Self::new(text, Default::default(), is_big, is_expanded) + fn clean(text: String, size: usize, is_expanded: bool) -> Self { + Self::new(text, Default::default(), size, is_expanded) } fn text(text: String) -> Self { @@ -86,7 +86,7 @@ impl CellOutput { } fn styled(text: NuText) -> Self { - Self::new(text.0, text.1, false, false) + Self::new(text.0, text.1, 1, false) } } @@ -117,7 +117,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { let with_index = has_index(&cfg.opts, &headers); let row_offset = cfg.opts.index_offset; - let mut is_footer_used = false; + let mut rows_count = 0usize; // The header with the INDEX is removed from the table headers since // it is added to the natural table index @@ -199,9 +199,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { data[row].push(value); data_styles.insert((row, with_index as usize), cell.style); - if cell.is_big { - is_footer_used = cell.is_big; - } + rows_count = rows_count.saturating_add(cell.size); } let mut table = NuTable::from(data); @@ -209,12 +207,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { table.set_index_style(get_index_style(cfg.opts.style_computer)); set_data_styles(&mut table, data_styles); - return Ok(Some(TableOutput::new( - table, - false, - with_index, - is_footer_used, - ))); + return Ok(Some(TableOutput::new(table, false, with_index, rows_count))); } if !headers.is_empty() { @@ -269,6 +262,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } } + let mut column_rows = 0usize; + for (row, item) in input.iter().enumerate() { cfg.opts.signals.check(cfg.opts.span)?; @@ -294,9 +289,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { data[row + 1].push(value); data_styles.insert((row + 1, col + with_index as usize), cell.style); - if cell.is_big { - is_footer_used = cell.is_big; - } + column_rows = column_rows.saturating_add(cell.size); } let head_cell = NuTableCell::new(header); @@ -316,6 +309,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { available_width -= pad_space + column_width; rendered_column += 1; + + rows_count = std::cmp::max(rows_count, column_rows); } if truncate && rendered_column == 0 { @@ -374,9 +369,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); set_data_styles(&mut table, data_styles); - let has_footer = is_footer_used || has_footer(&cfg.opts, table.count_rows() as u64); - - Ok(Some(TableOutput::new(table, true, with_index, has_footer))) + Ok(Some(TableOutput::new(table, true, with_index, rows_count))) } fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { @@ -395,7 +388,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { let value_width = cfg.opts.width - key_width - count_borders - padding - padding; - let mut with_footer = false; + let mut count_rows = 0usize; let mut data = Vec::with_capacity(record.len()); for (key, value) in record { @@ -420,19 +413,17 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { data.push(row); - if cell.is_big { - with_footer = cell.is_big; - } + count_rows = count_rows.saturating_add(cell.size); } let mut table = NuTable::from(data); table.set_index_style(get_key_style(&cfg)); table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); - let out = TableOutput::new(table, false, true, with_footer); + let out = TableOutput::new(table, false, true, count_rows); maybe_expand_table(out, cfg.opts.width, &cfg.opts) - .map(|value| value.map(|value| CellOutput::clean(value, with_footer, false))) + .map(|value| value.map(|value| CellOutput::clean(value, count_rows, false))) } // the flag is used as an optimization to not do `value.lines().count()` search. @@ -441,7 +432,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR if is_limited { return Ok(Some(CellOutput::clean( value_to_string_clean(value, cfg), - false, + 1, false, ))); } @@ -457,7 +448,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR let cfg = create_table_cfg(cfg, &out); let value = out.table.draw(cfg, value_width); match value { - Some(value) => Ok(Some(CellOutput::clean(value, out.with_footer, true))), + Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))), None => Ok(None), } } @@ -484,7 +475,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR let inner_cfg = update_config(dive_options(cfg, span), value_width); let result = expanded_table_kv(record, inner_cfg)?; match result { - Some(result) => Ok(Some(CellOutput::clean(result.text, result.is_big, true))), + Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))), None => Ok(Some(CellOutput::text(value_to_wrapped_string( value, cfg, @@ -575,7 +566,7 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput { let table_config = create_table_cfg(&cfg, &out); let table = out.table.draw(table_config, usize::MAX); match table { - Some(table) => CellOutput::clean(table, out.with_footer, false), + Some(table) => CellOutput::clean(table, out.count_rows, false), None => CellOutput::styled(nu_value_to_string( item, cfg.opts.config, diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index ba0a1ceefa..e748b6eff2 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -56,8 +56,9 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut table = NuTable::from(data); table.set_index_style(TextStyle::default_field()); + let count_rows = table.count_rows(); - let mut out = TableOutput::new(table, false, true, false); + let mut out = TableOutput::new(table, false, true, count_rows); let left = opts.config.table.padding.left; let right = opts.config.table.padding.right; @@ -82,7 +83,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { let with_header = !headers.is_empty(); if !with_header { let table = to_table_with_no_header(input, with_index, row_offset, opts)?; - let table = table.map(|table| TableOutput::new(table, false, with_index, false)); + let table = table.map(|table| { + let count_rows = table.count_rows(); + TableOutput::new(table, false, with_index, count_rows) + }); return Ok(table); } @@ -98,7 +102,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { .collect(); let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?; - let table = table.map(|table| TableOutput::new(table, true, with_index, false)); + let table = table.map(|table| { + let count_rows = table.count_rows(); + TableOutput::new(table, true, with_index, count_rows) + }); Ok(table) } diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index adbe56bf27..829c87ed9e 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -1,8 +1,7 @@ -use terminal_size::{terminal_size, Height, Width}; +use nu_color_config::StyleComputer; +use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; use crate::{common::INDEX_COLUMN_NAME, NuTable}; -use nu_color_config::StyleComputer; -use nu_protocol::{Config, FooterMode, Signals, Span, TableIndexMode, TableMode}; mod collapse; mod expanded; @@ -16,16 +15,16 @@ pub struct TableOutput { pub table: NuTable, pub with_header: bool, pub with_index: bool, - pub with_footer: bool, + pub count_rows: usize, } impl TableOutput { - pub fn new(table: NuTable, with_header: bool, with_index: bool, with_footer: bool) -> Self { + pub fn new(table: NuTable, with_header: bool, with_index: bool, count_rows: usize) -> Self { Self { table, with_header, with_index, - with_footer, + count_rows, } } } @@ -79,23 +78,3 @@ fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool { with_index && !opts.index_remove } - -fn has_footer(opts: &TableOpts<'_>, count_records: u64) -> bool { - match opts.config.footer_mode { - // Only show the footer if there are more than RowCount rows - FooterMode::RowCount(limit) => count_records > limit, - // Always show the footer - FooterMode::Always => true, - // Never show the footer - FooterMode::Never => false, - // Calculate the screen height and row count, if screen height is larger than row count, don't show footer - FooterMode::Auto => { - let (_width, height) = match terminal_size() { - Some((w, h)) => (Width(w.0).0 as u64, Height(h.0).0 as u64), - None => (Width(0).0 as u64, Height(0).0 as u64), - }; - - height <= count_records - } - } -} From eb0b6c87d610d222903d8c1b9a0523474e9176ff Mon Sep 17 00:00:00 2001 From: Ryan Faulhaber Date: Tue, 19 Nov 2024 17:20:52 -0500 Subject: [PATCH 09/43] Add mac and IP address entries to `sys net` (#14389) # Description What it says on the tin, this change adds the `mac` and `ip` columns to the `sys net` command, where `mac` is the interface mac address and `ip` is a record containing ipv4 and ipv6 addresses as well as whether or not the address is loopback and multicast. I thought it might be useful to have this information available in Nushell. This change basically just pulls extra information out of the underlying structs in the `sysinfo::Networks` struct. Here's a screenshot from my system: ![Screenshot from 2024-11-19 11-59-54](https://github.com/user-attachments/assets/92c2d72c-b0d0-49c0-8167-9e1ce853acf1) # User-Facing Changes - Adds `mac` and `ip` columns to the `sys net` command, where `mac` contains the interface's mac address and `ip` contains information extracted from the `std::net::IpAddr` struct, including address, protocol, whether or not the address is loopback, and whether or not it's multicast # Tests + Formatting Didn't add any tests specifically, didn't seem like there were any relevant tests. Ran existing tests and formatting. --- crates/nu-command/src/system/sys/net.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/nu-command/src/system/sys/net.rs b/crates/nu-command/src/system/sys/net.rs index ee82f73c61..8e2ac19bf5 100644 --- a/crates/nu-command/src/system/sys/net.rs +++ b/crates/nu-command/src/system/sys/net.rs @@ -44,8 +44,29 @@ fn net(span: Span) -> Value { let networks = Networks::new_with_refreshed_list() .iter() .map(|(iface, data)| { + let ip_addresses = data + .ip_networks() + .iter() + .map(|ip| { + let protocol = match ip.addr { + std::net::IpAddr::V4(_) => "ipv4", + std::net::IpAddr::V6(_) => "ipv6", + }; + Value::record( + record! { + "address" => Value::string(ip.addr.to_string(), span), + "protocol" => Value::string(protocol, span), + "loop" => Value::bool(ip.addr.is_loopback(), span), + "multicast" => Value::bool(ip.addr.is_multicast(), span), + }, + span, + ) + }) + .collect(); let record = record! { "name" => Value::string(trim_cstyle_null(iface), span), + "mac" => Value::string(data.mac_address().to_string(), span), + "ip" => Value::list(ip_addresses, span), "sent" => Value::filesize(data.total_transmitted() as i64, span), "recv" => Value::filesize(data.total_received() as i64, span), }; From 582b5f45e8cf6ffe82f5d84329eb7afcc8c40925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:19:24 +0800 Subject: [PATCH 10/43] Bump shadow-rs from 0.35.2 to 0.36.0 (#14396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 0.35.2 to 0.36.0.
Release notes

Sourced from shadow-rs's releases.

v0.36.0

What's Changed

Full Changelog: https://github.com/baoyachi/shadow-rs/compare/v0.35.2...v0.36.0

Commits
  • 909510e Merge pull request #190 from baoyachi/hook_ext
  • bad046d Update Cargo.toml
  • 84096a0 feat(HookExt): Add extended hook functionality with custom deny lists
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shadow-rs&package-manager=cargo&previous-version=0.35.2&new-version=0.36.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 | 4 ++-- crates/nu-cmd-lang/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2821dab891..887dfc1ffd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5893,9 +5893,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687" +checksum = "58cfcd0643497a9f780502063aecbcc4a3212cbe4948fd25ee8fd179c2cf9a18" dependencies = [ "const_format", "is_debug", diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index c5609e4ff8..6e8cb824ba 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -21,10 +21,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1" } itertools = { workspace = true } -shadow-rs = { version = "0.35", default-features = false } +shadow-rs = { version = "0.36", default-features = false } [build-dependencies] -shadow-rs = { version = "0.35", default-features = false } +shadow-rs = { version = "0.36", default-features = false } [features] mimalloc = [] From a6e3470c6f24b781280eaa36e8d610025c588ec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:19:37 +0800 Subject: [PATCH 11/43] Bump thiserror from 1.0.69 to 2.0.3 (#14394) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to 2.0.3.
Release notes

Sourced from thiserror's releases.

2.0.3

  • Support the same Path field being repeated in both Debug and Display representation in error message (#383)
  • Improve error message when a format trait used in error message is not implemented by some field (#384)

2.0.2

  • Fix hang on invalid input inside #[error(...)] attribute (#382)

2.0.1

  • Support errors that contain a dynamically sized final field (#375)
  • Improve inference of trait bounds for fields that are interpolated multiple times in an error message (#377)

2.0.0

Breaking changes

  • Referencing keyword-named fields by a raw identifier like {r#type} inside a format string is no longer accepted; simply use the unraw name like {type} (#347)

    This aligns thiserror with the standard library's formatting macros, which gained support for implicit argument capture later than the release of this feature in thiserror 1.x.

    #[derive(Error, Debug)]
    #[error("... {type} ...")]  // Before: {r#type}
    pub struct Error {
        pub r#type: Type,
    }
    
  • Trait bounds are no longer inferred on fields whose value is shadowed by an explicit named argument in a format message (#345)

    // Before: impl<T: Octal> Display for
    Error<T>
    // After: impl<T> Display for Error<T>
    #[derive(Error, Debug)]
    #[error("{thing:o}", thing = "...")]
    pub struct Error<T> {
        thing: T,
    }
    
  • Tuple structs and tuple variants can no longer use numerical {0} {1} access at the same time as supplying extra positional arguments for a format message, as this makes it ambiguous whether the number refers to a tuple field vs a different positional arg (#354)

    #[derive(Error, Debug)]
    #[error("ambiguous: {0} {}", $N)]
    // ^^^ Not allowed, use #[error("... {0} {n}", n = $N)]
    pub struct TupleError(i32);
    
  • Code containing invocations of thiserror's derive(Error) must now have a direct dependency on the thiserror crate regardless of the error data structure's contents (#368, #369, #370, #372)

Features

... (truncated)

Commits
  • 15fd26e Release 2.0.3
  • 7046023 Simplify how has_bonus_display is accumulated
  • 9cc1d0b Merge pull request #384 from dtolnay/nowrap
  • 1d040f3 Use Var wrapper only for Pointer formatting
  • 6a6132d Extend no-display ui test to cover another fmt trait
  • a061beb Merge pull request #383 from dtolnay/both
  • 6388293 Support Display and Debug of same path in error message
  • dc0359e Defer binding_value construction
  • 520343e Add test of Debug and Display of paths
  • 49be39d Release 2.0.2
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=thiserror&package-manager=cargo&previous-version=1.0.69&new-version=2.0.3)](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 | 4 ++-- crates/nu-plugin/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 887dfc1ffd..cdfa8a1ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3485,7 +3485,7 @@ dependencies = [ "nu-protocol", "nu-utils", "serde", - "thiserror 1.0.69", + "thiserror 2.0.3", "typetag", ] @@ -3591,7 +3591,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "typetag", "windows-sys 0.48.0", ] diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index a2c328375e..2c43a86c5e 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -21,7 +21,7 @@ nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-feat nu-utils = { path = "../nu-utils", version = "0.100.1" } log = { workspace = true } -thiserror = "1.0" +thiserror = "2.0" [dev-dependencies] serde = { workspace = true } diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 60d6c10acc..dfbcb7b307 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -36,7 +36,7 @@ num-format = { workspace = true } rmp-serde = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } -thiserror = "1.0" +thiserror = "2.0" typetag = "0.2" os_pipe = { workspace = true, features = ["io_safety"] } log = { workspace = true } From 1e7840c376695fc9096a07916e748258c80135b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:20:47 +0800 Subject: [PATCH 12/43] Bump terminal_size from 0.3.0 to 0.4.0 (#14393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [terminal_size](https://github.com/eminence/terminal-size) from 0.3.0 to 0.4.0.
Release notes

Sourced from terminal_size's releases.

v0.4.0

Breaking changes

The big change in this release is the API change in #66:

  • If you were using the terminal_size_using_fd or terminal_size_using_handle functions, these are now deprecated and unsafe. Instead you should use the terminal_size_of function, which does the same thing but is safer.

What's Changed

New Contributors

Full Changelog: https://github.com/eminence/terminal-size/compare/v0.3.0...v0.4.0

Commits
  • f6b81b5 Bump to version 0.4.0
  • 5cbc616 Merge pull request #64 from waywardmonkeys/update-ci
  • 68ceb8d Merge pull request #63 from waywardmonkeys/fix-typo
  • 5307747 Merge pull request #66 from sunfishcode/main
  • a29b904 Mark terminal_size_using_handle as unsafe too.
  • ea92388 Mark terminal_size_using_fd as unsafe.
  • 78e81fa Merge pull request #67 from eminence/windows-sys
  • c69ff4e Update windows-sys to 0.59
  • 76b0cae Merge pull request #62 from barrbrain/windows-sys
  • 56334c3 Update the API for I/O safety
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=terminal_size&package-manager=cargo&previous-version=0.3.0&new-version=0.4.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 | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdfa8a1ad5..7f43aaada7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3333,7 +3333,7 @@ dependencies = [ "sysinfo 0.32.0", "tabled", "tempfile", - "terminal_size 0.3.0", + "terminal_size 0.4.0", "titlecase", "toml 0.8.19", "trash", @@ -3378,7 +3378,7 @@ dependencies = [ "nu-path", "nu-protocol", "nu-utils", - "terminal_size 0.3.0", + "terminal_size 0.4.0", ] [[package]] @@ -3402,7 +3402,7 @@ dependencies = [ "nu-utils", "ratatui", "strip-ansi-escapes", - "terminal_size 0.3.0", + "terminal_size 0.4.0", "unicode-width 0.1.11", ] @@ -3635,7 +3635,7 @@ dependencies = [ "nu-protocol", "nu-utils", "tabled", - "terminal_size 0.3.0", + "terminal_size 0.4.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ad2e886ad8..b9be5c59f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,7 @@ syn = "2.0" sysinfo = "0.32" tabled = { version = "0.16.0", default-features = false } tempfile = "3.14" -terminal_size = "0.3" +terminal_size = "0.4" titlecase = "2.0" toml = "0.8" trash = "5.2" From 5d1eb031ebb4dec3bed95e62286e66a2fd3fea6d Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 20 Nov 2024 03:24:03 -0800 Subject: [PATCH 13/43] Turn compile errors into fatal errors (#14388) # Description Because the IR compiler was previously optional, compile errors were not treated as fatal errors, and were just logged like parse warnings are. This unfortunately meant that if a user encountered a compile error, they would see "Can't evaluate block in IR mode" as the actual error in addition to (hopefully) logging the compile error. This changes compile errors to be treated like parse errors so that they show up as the last error, helping users understand what's wrong a little bit more easily. Fixes #14333. # User-Facing Changes - Shouldn't see "Can't evaluate block in IR mode" - Should only see compile error - No evaluation should happen # Tests + Formatting Didn't add any tests specifically for this, but it might be good to have at least one that checks to ensure the compile error shows up and the "can't evaluate" error does not. --- crates/nu-cli/src/eval_cmds.rs | 2 +- crates/nu-cli/src/eval_file.rs | 2 +- crates/nu-cli/src/util.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 663185a4a2..5fba00f5c9 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -74,7 +74,7 @@ pub fn evaluate_commands( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + std::process::exit(1); } (output, working_set.render()) diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index 7636adc8d4..826b6c8eb5 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -89,7 +89,7 @@ pub fn evaluate_file( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + std::process::exit(1); } // Look for blocks whose name starts with "main" and replace it with the filename. diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 3e69657857..7b9d783534 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -296,7 +296,7 @@ fn evaluate_source( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + return Ok(true); } (output, working_set.render()) From 42d2adc3e037d2ceb3d3708407b148efa7896c96 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 20 Nov 2024 07:55:26 -0600 Subject: [PATCH 14/43] allow ps1 files to be executed without pwsh/powershell -c file.ps1 (#14379) # Description This PR allows nushell to run powershell scripts easier. You can already do `powershell -c script.ps1` but this PR takes it a step further by doing the `powershell -c` part for you. So, if you have script.ps1 you can execute it by running it in the command position of the repl. ![image](https://github.com/user-attachments/assets/0661a746-27d9-4d21-b576-c244ff7fab2b) or once it's in json, just consume it with nushell. ![image](https://github.com/user-attachments/assets/38f5c5d8-3659-41f0-872b-91a14909760b) # User-Facing Changes Easier to run powershell scripts. It should work on Windows with powershell.exe. # Tests + Formatting Added 1 test # After Submitting --------- Co-authored-by: Wind --- crates/nu-command/src/system/run_external.rs | 45 +++++++++++++++++-- .../nu-command/tests/commands/run_external.rs | 41 +++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 604278c676..ffb34bf077 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -5,6 +5,8 @@ use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDe use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; +#[cfg(windows)] +use std::os::windows::process::CommandExt; use std::{ borrow::Cow, ffi::{OsStr, OsString}, @@ -91,6 +93,22 @@ impl Command for External { false }; + // let's make sure it's a .ps1 script, but only on Windows + let potential_powershell_script = if cfg!(windows) { + if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) { + let ext = executable + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_uppercase(); + ext == "PS1" + } else { + false + } + } else { + false + }; + // Find the absolute path to the executable. On Windows, set the // executable to "cmd.exe" if it's a CMD internal command. If the // command is not found, display a helpful error message. @@ -98,11 +116,16 @@ impl Command for External { && (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows) { PathBuf::from("cmd.exe") + } else if cfg!(windows) && potential_powershell_script { + // If we're on Windows and we're trying to run a PowerShell script, we'll use + // `powershell.exe` to run it. We shouldn't have to check for powershell.exe because + // it's automatically installed on all modern windows systems. + PathBuf::from("powershell.exe") } else { // 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.as_ref()) else { + let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else { return Err(command_not_found(&name_str, call.head, engine_state, stack)); }; executable @@ -123,15 +146,29 @@ impl Command for External { let args = eval_arguments_from_call(engine_state, stack, call)?; #[cfg(windows)] if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows { - 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_str]); + command.args(["/D", "/C", &expanded_name.to_string_lossy()]); for arg in &args { command.raw_arg(escape_cmd_argument(arg)?); } + } else if potential_powershell_script { + use nu_path::canonicalize_with; + + // canonicalize the path to the script so that tests pass + let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) { + canonicalize_with(&expanded_name, cwd)? + } else { + // If we can't get the current working directory, just provide the expanded name + expanded_name + }; + // The -Command flag followed by a script name instructs PowerShell to + // execute that script and quit. + command.args(["-Command", &canon_path.to_string_lossy()]); + for arg in &args { + command.raw_arg(arg.item.clone()); + } } else { command.args(args.into_iter().map(|s| s.item)); } diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 17667c9bb3..8a797300f1 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -355,9 +355,9 @@ fn external_command_receives_raw_binary_data() { #[cfg(windows)] #[test] -fn can_run_batch_files() { +fn can_run_cmd_files() { use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("run a Windows batch file", |dirs, sandbox| { + Playground::setup("run a Windows cmd file", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( "foo.cmd", r#" @@ -371,12 +371,30 @@ fn can_run_batch_files() { }); } +#[cfg(windows)] +#[test] +fn can_run_batch_files() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("run a Windows batch file", |dirs, sandbox| { + sandbox.with_files(&[FileWithContent( + "foo.bat", + r#" + @echo off + echo Hello World + "#, + )]); + + let actual = nu!(cwd: dirs.test(), pipeline("foo.bat")); + assert!(actual.out.contains("Hello World")); + }); +} + #[cfg(windows)] #[test] fn can_run_batch_files_without_cmd_extension() { use nu_test_support::fs::Stub::FileWithContent; Playground::setup( - "run a Windows batch file without specifying the extension", + "run a Windows cmd file without specifying the extension", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( "foo.cmd", @@ -440,3 +458,20 @@ fn redirect_combine() { assert_eq!(actual.out, "FooBar"); }); } + +#[cfg(windows)] +#[test] +fn can_run_ps1_files() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("run_a_windows_ps_file", |dirs, sandbox| { + sandbox.with_files(&[FileWithContent( + "foo.ps1", + r#" + Write-Host Hello World + "#, + )]); + + let actual = nu!(cwd: dirs.test(), pipeline("foo.ps1")); + assert!(actual.out.contains("Hello World")); + }); +} From b318d588fe9abe7268e8658647ab99a555b6e3a6 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:39:15 -0600 Subject: [PATCH 15/43] add new --flatten parameter to the ast command (#14400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description By request, this PR introduces a new `--flatten` parameter to the ast command for generating a more readable version of the AST output. This enhancement improves usability by allowing users to easily visualize the structure of the AST. ![image](https://github.com/user-attachments/assets/a66644ef-5fff-4d3d-a334-4e9f80edb39d) ```nushell ❯ ast 'ls | sort-by type name -i' --flatten --json [ { "content": "ls", "shape": "shape_internalcall", "span": { "start": 0, "end": 2 } }, { "content": "|", "shape": "shape_pipe", "span": { "start": 3, "end": 4 } }, { "content": "sort-by", "shape": "shape_internalcall", "span": { "start": 5, "end": 12 } }, { "content": "type", "shape": "shape_string", "span": { "start": 13, "end": 17 } }, { "content": "name", "shape": "shape_string", "span": { "start": 18, "end": 22 } }, { "content": "-i", "shape": "shape_flag", "span": { "start": 23, "end": 25 } } ] ❯ ast 'ls | sort-by type name -i' --flatten --json --minify [{"content":"ls","shape":"shape_internalcall","span":{"start":0,"end":2}},{"content":"|","shape":"shape_pipe","span":{"start":3,"end":4}},{"content":"sort-by","shape":"shape_internalcall","span":{"start":5,"end":12}},{"content":"type","shape":"shape_string","span":{"start":13,"end":17}},{"content":"name","shape":"shape_string","span":{"start":18,"end":22}},{"content":"-i","shape":"shape_flag","span":{"start":23,"end":25}}] ``` # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/debug/ast.rs | 346 +++++++++++++++++++++-------- 1 file changed, 250 insertions(+), 96 deletions(-) diff --git a/crates/nu-command/src/debug/ast.rs b/crates/nu-command/src/debug/ast.rs index c6e405d533..45e7738c32 100644 --- a/crates/nu-command/src/debug/ast.rs +++ b/crates/nu-command/src/debug/ast.rs @@ -1,6 +1,7 @@ use nu_engine::command_prelude::*; -use nu_parser::parse; -use nu_protocol::engine::StateWorkingSet; +use nu_parser::{flatten_block, parse}; +use nu_protocol::{engine::StateWorkingSet, record}; +use serde_json::{json, Value as JsonValue}; #[derive(Clone)] pub struct Ast; @@ -16,109 +17,23 @@ impl Command for Ast { fn signature(&self) -> Signature { Signature::build("ast") - .input_output_types(vec![(Type::String, Type::record())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::Nothing, Type::record()), + (Type::Nothing, Type::String), + ]) .required( "pipeline", SyntaxShape::String, "The pipeline to print the ast for.", ) - .switch("json", "serialize to json", Some('j')) - .switch("minify", "minify the nuon or json output", Some('m')) + .switch("json", "Serialize to json", Some('j')) + .switch("minify", "Minify the nuon or json output", Some('m')) + .switch("flatten", "An easier to read version of the ast", Some('f')) .allow_variants_without_examples(true) .category(Category::Debug) } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - _input: PipelineData, - ) -> Result { - let pipeline: Spanned = call.req(engine_state, stack, 0)?; - let to_json = call.has_flag(engine_state, stack, "json")?; - let minify = call.has_flag(engine_state, stack, "minify")?; - let mut working_set = StateWorkingSet::new(engine_state); - let block_output = parse(&mut working_set, None, pipeline.item.as_bytes(), false); - let error_output = working_set.parse_errors.first(); - let block_span = match &block_output.span { - Some(span) => span, - None => &pipeline.span, - }; - if to_json { - // Get the block as json - let serde_block_str = if minify { - serde_json::to_string(&*block_output) - } else { - serde_json::to_string_pretty(&*block_output) - }; - let block_json = match serde_block_str { - Ok(json) => json, - Err(e) => Err(ShellError::CantConvert { - to_type: "string".to_string(), - from_type: "block".to_string(), - span: *block_span, - help: Some(format!( - "Error: {e}\nCan't convert {block_output:?} to string" - )), - })?, - }; - // Get the error as json - let serde_error_str = if minify { - serde_json::to_string(&error_output) - } else { - serde_json::to_string_pretty(&error_output) - }; - - let error_json = match serde_error_str { - Ok(json) => json, - Err(e) => Err(ShellError::CantConvert { - to_type: "string".to_string(), - from_type: "error".to_string(), - span: *block_span, - help: Some(format!( - "Error: {e}\nCan't convert {error_output:?} to string" - )), - })?, - }; - - // Create a new output record, merging the block and error - let output_record = Value::record( - record! { - "block" => Value::string(block_json, *block_span), - "error" => Value::string(error_json, Span::test_data()), - }, - pipeline.span, - ); - Ok(output_record.into_pipeline_data()) - } else { - let block_value = Value::string( - if minify { - format!("{block_output:?}") - } else { - format!("{block_output:#?}") - }, - pipeline.span, - ); - let error_value = Value::string( - if minify { - format!("{error_output:?}") - } else { - format!("{error_output:#?}") - }, - pipeline.span, - ); - let output_record = Value::record( - record! { - "block" => block_value, - "error" => error_value - }, - pipeline.span, - ); - Ok(output_record.into_pipeline_data()) - } - } - fn examples(&self) -> Vec { vec![ Example { @@ -147,8 +62,247 @@ impl Command for Ast { example: "ast 'for x in 1..10 { echo $x ' --json --minify", result: None, }, + Example { + description: "Print the ast of a string flattened", + example: r#"ast "'hello'" --flatten"#, + result: Some(Value::test_list(vec![Value::test_record(record! { + "content" => Value::test_string("'hello'"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(0), + "end" => Value::test_int(7),}), + })])), + }, + Example { + description: "Print the ast of a string flattened, as json, minified", + example: r#"ast "'hello'" --flatten --json --minify"#, + result: Some(Value::test_string( + r#"[{"content":"'hello'","shape":"shape_string","span":{"start":0,"end":7}}]"#, + )), + }, + Example { + description: "Print the ast of a pipeline flattened", + example: r#"ast 'ls | sort-by type name -i' --flatten"#, + result: Some(Value::test_list(vec![ + Value::test_record(record! { + "content" => Value::test_string("ls"), + "shape" => Value::test_string("shape_external"), + "span" => Value::test_record(record! { + "start" => Value::test_int(0), + "end" => Value::test_int(2),}), + }), + Value::test_record(record! { + "content" => Value::test_string("|"), + "shape" => Value::test_string("shape_pipe"), + "span" => Value::test_record(record! { + "start" => Value::test_int(3), + "end" => Value::test_int(4),}), + }), + Value::test_record(record! { + "content" => Value::test_string("sort-by"), + "shape" => Value::test_string("shape_internalcall"), + "span" => Value::test_record(record! { + "start" => Value::test_int(5), + "end" => Value::test_int(12),}), + }), + Value::test_record(record! { + "content" => Value::test_string("type"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(13), + "end" => Value::test_int(17),}), + }), + Value::test_record(record! { + "content" => Value::test_string("name"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(18), + "end" => Value::test_int(22),}), + }), + Value::test_record(record! { + "content" => Value::test_string("-i"), + "shape" => Value::test_string("shape_flag"), + "span" => Value::test_record(record! { + "start" => Value::test_int(23), + "end" => Value::test_int(25),}), + }), + ])), + }, ] } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let pipeline: Spanned = call.req(engine_state, stack, 0)?; + let to_json = call.has_flag(engine_state, stack, "json")?; + let minify = call.has_flag(engine_state, stack, "minify")?; + let flatten = call.has_flag(engine_state, stack, "flatten")?; + + let mut working_set = StateWorkingSet::new(engine_state); + let offset = working_set.next_span_start(); + let parsed_block = parse(&mut working_set, None, pipeline.item.as_bytes(), false); + + if flatten { + let flat = flatten_block(&working_set, &parsed_block); + if to_json { + let mut json_val: JsonValue = json!([]); + for (span, shape) in flat { + let content = + String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); + + let json = json!( + { + "content": content, + "shape": shape.to_string(), + "span": { + "start": span.start.checked_sub(offset), + "end": span.end.checked_sub(offset), + }, + } + ); + json_merge(&mut json_val, &json); + } + let json_string = if minify { + if let Ok(json_str) = serde_json::to_string(&json_val) { + json_str + } else { + "{}".to_string() + } + } else if let Ok(json_str) = serde_json::to_string_pretty(&json_val) { + json_str + } else { + "{}".to_string() + }; + + Ok(Value::string(json_string, pipeline.span).into_pipeline_data()) + } else { + // let mut rec: Record = Record::new(); + let mut rec = vec![]; + for (span, shape) in flat { + let content = + String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); + let each_rec = record! { + "content" => Value::test_string(content), + "shape" => Value::test_string(shape.to_string()), + "span" => Value::test_record(record!{ + "start" => Value::test_int(match span.start.checked_sub(offset) { + Some(start) => start as i64, + None => 0 + }), + "end" => Value::test_int(match span.end.checked_sub(offset) { + Some(end) => end as i64, + None => 0 + }), + }), + }; + rec.push(Value::test_record(each_rec)); + } + Ok(Value::list(rec, pipeline.span).into_pipeline_data()) + } + } else { + let error_output = working_set.parse_errors.first(); + let block_span = match &parsed_block.span { + Some(span) => span, + None => &pipeline.span, + }; + if to_json { + // Get the block as json + let serde_block_str = if minify { + serde_json::to_string(&*parsed_block) + } else { + serde_json::to_string_pretty(&*parsed_block) + }; + let block_json = match serde_block_str { + Ok(json) => json, + Err(e) => Err(ShellError::CantConvert { + to_type: "string".to_string(), + from_type: "block".to_string(), + span: *block_span, + help: Some(format!( + "Error: {e}\nCan't convert {parsed_block:?} to string" + )), + })?, + }; + // Get the error as json + let serde_error_str = if minify { + serde_json::to_string(&error_output) + } else { + serde_json::to_string_pretty(&error_output) + }; + + let error_json = match serde_error_str { + Ok(json) => json, + Err(e) => Err(ShellError::CantConvert { + to_type: "string".to_string(), + from_type: "error".to_string(), + span: *block_span, + help: Some(format!( + "Error: {e}\nCan't convert {error_output:?} to string" + )), + })?, + }; + + // Create a new output record, merging the block and error + let output_record = Value::record( + record! { + "block" => Value::string(block_json, *block_span), + "error" => Value::string(error_json, Span::test_data()), + }, + pipeline.span, + ); + Ok(output_record.into_pipeline_data()) + } else { + let block_value = Value::string( + if minify { + format!("{parsed_block:?}") + } else { + format!("{parsed_block:#?}") + }, + pipeline.span, + ); + let error_value = Value::string( + if minify { + format!("{error_output:?}") + } else { + format!("{error_output:#?}") + }, + pipeline.span, + ); + let output_record = Value::record( + record! { + "block" => block_value, + "error" => error_value + }, + pipeline.span, + ); + Ok(output_record.into_pipeline_data()) + } + } + } +} + +fn json_merge(a: &mut JsonValue, b: &JsonValue) { + match (a, b) { + (JsonValue::Object(ref mut a), JsonValue::Object(b)) => { + for (k, v) in b { + json_merge(a.entry(k).or_insert(JsonValue::Null), v); + } + } + (JsonValue::Array(ref mut a), JsonValue::Array(b)) => { + a.extend(b.clone()); + } + (JsonValue::Array(ref mut a), JsonValue::Object(b)) => { + a.extend([JsonValue::Object(b.clone())]); + } + (a, b) => { + *a = b.clone(); + } + } } #[cfg(test)] From 4ed25b63a6fc3444bb870dd6fa2c5a9abb936b00 Mon Sep 17 00:00:00 2001 From: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:15:15 -0500 Subject: [PATCH 16/43] Always load default env/config values (#14249) # Release-Notes Short Description * Nushell now always loads its internal `default_env.nu` before the user `env.nu` is loaded, then loads the internal `default_config.nu` before the user's `config.nu` is loaded. This allows for a simpler user-configuration experience. The Configuration Chapter of the Book will be updated soon with the new behavior. # Description Implements the main ideas in #13671 and a few more: * Users can now specify only the environment and config options they want to override in *their* `env.nu` and `config.nu`and yet still have access to all of the defaults: * `default_env.nu` (internally defined) will be loaded whenever (and before) the user's `env.nu` is loaded. * `default_config.nu` (internally defined) will be loaded whenever (and before) the user's `config.nu` is loaded. * No more 900+ line config out-of-the-box. * Faster startup (again): ~40-45% improvement in launch time with a default configuration. * New keys that are added to the defaults in the future will automatically be available to all users after updating Nushell. No need to regenerate config to get the new defaults. * It is now possible to have different internal defaults (which will be used with `-c` and scripts) vs. REPL defaults. This would have solved many of the user complaints about the [`display_errors` implementation](https://www.nushell.sh/blog/2024-09-17-nushell_0_98_0.html#non-zero-exit-codes-are-now-errors-toc). * A basic "scaffold" `config.nu` and `env.nu` are created on first launch (if the config directory isn't present). * Improved "out-of-the-box" experience (OOBE) - No longer asks to create the files; the minimal scaffolding will be automatically created. If deleted, they will not be regenerated. This provides a better "out-of-the-box" experience for the user as they no longer have to make this decision (without much info on the pros or cons) when first launching. * (New: 2024-11-07) Runs the env_conversions process after the `default_env.nu` is loaded so that users can treat `Path`/`PATH` as lists in their own config. * (New: 2024-11-08) Given the changes in #13802, `default_config.nu` will be a minimal file to minimize load-times. This shaves another (on my system) ~3ms off the base launch time. * Related: Keybindings, menus, and hooks that are already internal defaults are no longer duplicated in `$env.config`. The documentation will be updated to cover these scenarios. * (New: 2024-11-08) Move existing "full" `default_config.nu` to `sample_config.nu` for short-term "documentation" purposes. * (New: 2024-11-18) Move the `dark-theme` and `light-theme` to Standard Library and demonstrate their use - Also improves startup times, but we're reaching the limit of optimization. * (New: 2024-11-18) Extensively documented/commented `sample_env.nu` and `sample_config.nu`. These can be displayed in-shell using (for example) `config nu --sample | nu-highlight | less -R`. Note: Much of this will eventually be moved to or (some) duplicated in the Doc. But for now, this some nice in-shell doc that replaces the older "commented/documented default". * (New: 2024-11-20) Runs the `ENV_CONVERSIONS` process (1) after the `default_env.nu` (allows `PATH` to be used as a list in user's `env.nu`) and (2) before `default_config.nu` is loaded (allows user's `ENV_CONVERSIONS` from their `env.nu` to be used in their `config.nu`). * (New: 2024-11-20) The default `ENV_CONVERSIONS` is now an empty record. The internal Rust code handles `PATH` (and variants) conversions regardless of the `ENV_CONVERSIONS` variable. This shaves a *very* small amount of time off the startup. Reset - Looks like there might be a bug in `nu-enginer::env::ensure_path()` on Windows that would need to be fixed in order for this to work. # User-Facing Changes By default, you shouldn't see much, if any, change when running this with your existing configuration. To see the greatest benefit from these changes, you'll probably want to start with a "fresh" config. This can be easily tested using something like: ```nushell let temp_home = (mktemp -d) $env.XDG_CONFIG_HOME = $temp_home $env.XDG_DATA_HOME = $temp_home ./target/release/nu ``` You should see a message where the (mostly empty) `env.nu` and `config.nu` are created on first start. Defaults should be the same (or similar to) those before the PR. Please let me know if you notice any differences. --- Users should now specify configuration in terms of overrides of each setting. For instance, rather than modifying `history` settings in the monolithic `config.nu`, the following is recommended in an updated `config.nu`: ```nu $env.config.history = { file_format: sqlite, sync_on_enter: true isolation: true max_size: 1_000_000 } ``` or even just: ```nu $env.config.history.file_format = sqlite $env.config.history.isolation: true $env.config.history.max_size = 1_000_000 ``` Note: It seems many users are already appending a `source my_config.nu` (or similar pattern) to the end of the existing `config.nu` to make updates easier. In this case, they will likely want to remove all of the previous defaults and just move their `my_config.nu` to `config.nu`. Note: It should be unlikely that there are any breaking changes here, but there's a slim chance that some code, somewhere, *expects* an absence of certain config values. Otherwise, all config values are available before and after this change. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting Configuration Chapter (and related) of the doc is currently WIP and will be finished in time for 0.101 release. --- .../nu-command/src/env/config/config_env.rs | 37 +- crates/nu-command/src/env/config/config_nu.rs | 37 +- crates/nu-protocol/tests/into_config.rs | 14 +- crates/nu-std/src/lib.rs | 1 + crates/nu-std/std/config/mod.nu | 139 +++ crates/nu-std/std/mod.nu | 1 + crates/nu-utils/src/default_files/README.md | 82 ++ .../src/default_files/default_config.nu | 63 ++ .../nu-utils/src/default_files/default_env.nu | 57 ++ .../src/default_files/sample_config.nu | 792 +++++++++++++++ .../nu-utils/src/default_files/sample_env.nu | 135 +++ .../sample_login.nu | 0 .../src/default_files/scaffold_config.nu | 19 + .../src/default_files/scaffold_env.nu | 18 + crates/nu-utils/src/lib.rs | 5 +- .../src/sample_config/default_config.nu | 899 ------------------ .../nu-utils/src/sample_config/default_env.nu | 101 -- crates/nu-utils/src/utils.rs | 21 +- src/config_files.rs | 98 +- src/run.rs | 14 +- tests/repl/test_config_path.rs | 4 +- 21 files changed, 1454 insertions(+), 1083 deletions(-) create mode 100644 crates/nu-std/std/config/mod.nu create mode 100644 crates/nu-utils/src/default_files/README.md create mode 100644 crates/nu-utils/src/default_files/default_config.nu create mode 100644 crates/nu-utils/src/default_files/default_env.nu create mode 100644 crates/nu-utils/src/default_files/sample_config.nu create mode 100644 crates/nu-utils/src/default_files/sample_env.nu rename crates/nu-utils/src/{sample_config => default_files}/sample_login.nu (100%) create mode 100644 crates/nu-utils/src/default_files/scaffold_config.nu create mode 100644 crates/nu-utils/src/default_files/scaffold_env.nu delete mode 100644 crates/nu-utils/src/sample_config/default_config.nu delete mode 100644 crates/nu-utils/src/sample_config/default_env.nu diff --git a/crates/nu-command/src/env/config/config_env.rs b/crates/nu-command/src/env/config/config_env.rs index cb4a217d9b..5331d2d3be 100644 --- a/crates/nu-command/src/env/config/config_env.rs +++ b/crates/nu-command/src/env/config/config_env.rs @@ -15,7 +15,16 @@ impl Command for ConfigEnv { Signature::build(self.name()) .category(Category::Env) .input_output_types(vec![(Type::Nothing, Type::Any)]) - .switch("default", "Print default `env.nu` file instead.", Some('d')) + .switch( + "default", + "Print the internal default `env.nu` file instead.", + Some('d'), + ) + .switch( + "sample", + "Print a commented, sample `env.nu` file instead.", + Some('s'), + ) // TODO: Signature narrower than what run actually supports theoretically } @@ -26,18 +35,18 @@ impl Command for ConfigEnv { fn examples(&self) -> Vec { vec![ Example { - description: "allow user to open and update nu env", + description: "open user's env.nu in the default editor", example: "config env", result: None, }, Example { - description: "allow user to print default `env.nu` file", - example: "config env --default,", + description: "pretty-print a commented, sample `env.nu` that explains common settings", + example: "config env --sample | nu-highlight,", result: None, }, Example { - description: "allow saving the default `env.nu` locally", - example: "config env --default | save -f ~/.config/nushell/default_env.nu", + description: "pretty-print the internal `env.nu` file which is loaded before the user's environment", + example: "config env --default | nu-highlight,", result: None, }, ] @@ -50,12 +59,28 @@ impl Command for ConfigEnv { call: &Call, _input: PipelineData, ) -> Result { + let default_flag = call.has_flag(engine_state, stack, "default")?; + let sample_flag = call.has_flag(engine_state, stack, "sample")?; + if default_flag && sample_flag { + return Err(ShellError::IncompatibleParameters { + left_message: "can't use `--default` at the same time".into(), + left_span: call.get_flag_span(stack, "default").expect("has flag"), + right_message: "because of `--sample`".into(), + right_span: call.get_flag_span(stack, "sample").expect("has flag"), + }); + } // `--default` flag handling if call.has_flag(engine_state, stack, "default")? { let head = call.head; return Ok(Value::string(nu_utils::get_default_env(), head).into_pipeline_data()); } + // `--sample` flag handling + if sample_flag { + let head = call.head; + return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data()); + } + // Find the editor executable. let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; diff --git a/crates/nu-command/src/env/config/config_nu.rs b/crates/nu-command/src/env/config/config_nu.rs index 9969968ed2..176643a76f 100644 --- a/crates/nu-command/src/env/config/config_nu.rs +++ b/crates/nu-command/src/env/config/config_nu.rs @@ -17,9 +17,14 @@ impl Command for ConfigNu { .input_output_types(vec![(Type::Nothing, Type::Any)]) .switch( "default", - "Print default `config.nu` file instead.", + "Print the internal default `config.nu` file instead.", Some('d'), ) + .switch( + "sample", + "Print a commented, sample `config.nu` file instead.", + Some('s'), + ) // TODO: Signature narrower than what run actually supports theoretically } @@ -30,18 +35,19 @@ impl Command for ConfigNu { fn examples(&self) -> Vec { vec![ Example { - description: "allow user to open and update nu config", + description: "open user's config.nu in the default editor", example: "config nu", result: None, }, Example { - description: "allow user to print default `config.nu` file", - example: "config nu --default,", + description: "pretty-print a commented, sample `config.nu` that explains common settings", + example: "config nu --sample | nu-highlight", result: None, }, Example { - description: "allow saving the default `config.nu` locally", - example: "config nu --default | save -f ~/.config/nushell/default_config.nu", + description: + "pretty-print the internal `config.nu` file which is loaded before user's config", + example: "config nu --default | nu-highlight", result: None, }, ] @@ -54,12 +60,29 @@ impl Command for ConfigNu { call: &Call, _input: PipelineData, ) -> Result { + let default_flag = call.has_flag(engine_state, stack, "default")?; + let sample_flag = call.has_flag(engine_state, stack, "sample")?; + if default_flag && sample_flag { + return Err(ShellError::IncompatibleParameters { + left_message: "can't use `--default` at the same time".into(), + left_span: call.get_flag_span(stack, "default").expect("has flag"), + right_message: "because of `--sample`".into(), + right_span: call.get_flag_span(stack, "sample").expect("has flag"), + }); + } + // `--default` flag handling - if call.has_flag(engine_state, stack, "default")? { + if default_flag { let head = call.head; return Ok(Value::string(nu_utils::get_default_config(), head).into_pipeline_data()); } + // `--sample` flag handling + if sample_flag { + let head = call.head; + return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data()); + } + // Find the editor executable. let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; diff --git a/crates/nu-protocol/tests/into_config.rs b/crates/nu-protocol/tests/into_config.rs index 63d7eb8b22..3ff8d44672 100644 --- a/crates/nu-protocol/tests/into_config.rs +++ b/crates/nu-protocol/tests/into_config.rs @@ -35,7 +35,7 @@ fn config_affected_when_mutated() { #[test] fn config_affected_when_deep_mutated() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[ + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[ r#"source default_config.nu"#, r#"$env.config.filesize.metric = true"#, r#"20mib | into string"#])); @@ -45,7 +45,7 @@ fn config_affected_when_deep_mutated() { #[test] fn config_add_unsupported_key() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[ + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[ r#"source default_config.nu"#, r#"$env.config.foo = 2"#, r#";"#])); @@ -57,7 +57,7 @@ fn config_add_unsupported_key() { #[test] fn config_add_unsupported_type() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[r#"source default_config.nu"#, + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[r#"source default_config.nu"#, r#"$env.config.ls = '' "#, r#";"#])); @@ -66,7 +66,7 @@ fn config_add_unsupported_type() { #[test] fn config_add_unsupported_value() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[r#"source default_config.nu"#, + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[r#"source default_config.nu"#, r#"$env.config.history.file_format = ''"#, r#";"#])); @@ -77,7 +77,7 @@ fn config_add_unsupported_value() { #[test] #[ignore = "Figure out how to make test_bins::nu_repl() continue execution after shell errors"] fn config_unsupported_key_reverted() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[r#"source default_config.nu"#, + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[r#"source default_config.nu"#, r#"$env.config.foo = 1"#, r#"'foo' in $env.config"#])); @@ -87,7 +87,7 @@ fn config_unsupported_key_reverted() { #[test] #[ignore = "Figure out how to make test_bins::nu_repl() continue execution after shell errors"] fn config_unsupported_type_reverted() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[r#" source default_config.nu"#, + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[r#" source default_config.nu"#, r#"$env.config.ls = ''"#, r#"$env.config.ls | describe"#])); @@ -97,7 +97,7 @@ fn config_unsupported_type_reverted() { #[test] #[ignore = "Figure out how to make test_bins::nu_repl() continue execution after errors"] fn config_unsupported_value_reverted() { - let actual = nu!(cwd: "crates/nu-utils/src/sample_config", nu_repl_code(&[r#" source default_config.nu"#, + let actual = nu!(cwd: "crates/nu-utils/src/default_files", nu_repl_code(&[r#" source default_config.nu"#, r#"$env.config.history.file_format = 'plaintext'"#, r#"$env.config.history.file_format = ''"#, r#"$env.config.history.file_format | to json"#])); diff --git a/crates/nu-std/src/lib.rs b/crates/nu-std/src/lib.rs index ebc6ee0053..b03b710cfc 100644 --- a/crates/nu-std/src/lib.rs +++ b/crates/nu-std/src/lib.rs @@ -54,6 +54,7 @@ pub fn load_standard_library( ("mod.nu", "std/math", include_str!("../std/math/mod.nu")), ("mod.nu", "std/util", include_str!("../std/util/mod.nu")), ("mod.nu", "std/xml", include_str!("../std/xml/mod.nu")), + ("mod.nu", "std/config", include_str!("../std/config/mod.nu")), ]; for (filename, std_subdir_name, content) in std_submodules.drain(..) { diff --git a/crates/nu-std/std/config/mod.nu b/crates/nu-std/std/config/mod.nu new file mode 100644 index 0000000000..b73964e8d3 --- /dev/null +++ b/crates/nu-std/std/config/mod.nu @@ -0,0 +1,139 @@ +# Returns a dark-mode theme that can be assigned to $env.config.color_config +export def dark-theme [] { + { + # color for nushell primitives + separator: white + leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off + header: green_bold + empty: blue + # Closures can be used to choose colors for specific values. + # The value (in this case, a bool) is piped into the closure. + # eg) {|| if $in { 'light_cyan' } else { 'light_gray' } } + bool: light_cyan + int: white + filesize: cyan + duration: white + date: purple + range: white + float: white + string: white + nothing: white + binary: white + cell-path: white + row_index: green_bold + record: white + list: white + block: white + hints: dark_gray + search_result: { bg: red fg: white } + shape_and: purple_bold + shape_binary: purple_bold + shape_block: blue_bold + shape_bool: light_cyan + shape_closure: green_bold + shape_custom: green + shape_datetime: cyan_bold + shape_directory: cyan + shape_external: cyan + shape_externalarg: green_bold + shape_external_resolved: light_yellow_bold + shape_filepath: cyan + shape_flag: blue_bold + 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 + shape_keyword: cyan_bold + shape_list: cyan_bold + shape_literal: blue + shape_match_pattern: green + shape_matching_brackets: { attr: u } + shape_nothing: light_cyan + shape_operator: yellow + shape_or: purple_bold + shape_pipe: purple_bold + shape_range: yellow_bold + shape_record: cyan_bold + shape_redirection: purple_bold + shape_signature: green_bold + shape_string: green + shape_string_interpolation: cyan_bold + shape_table: blue_bold + shape_variable: purple + shape_vardecl: purple + shape_raw_string: light_purple + } +} + +# Returns a light-mode theme that can be assigned to $env.config.color_config +export def light-theme [] { + { + # color for nushell primitives + separator: dark_gray + leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off + header: green_bold + empty: blue + # Closures can be used to choose colors for specific values. + # The value (in this case, a bool) is piped into the closure. + # eg) {|| if $in { 'dark_cyan' } else { 'dark_gray' } } + bool: dark_cyan + int: dark_gray + filesize: cyan_bold + duration: dark_gray + date: purple + range: dark_gray + float: dark_gray + string: dark_gray + nothing: dark_gray + binary: dark_gray + cell-path: dark_gray + row_index: green_bold + record: dark_gray + list: dark_gray + block: dark_gray + hints: dark_gray + search_result: { fg: white bg: red } + shape_and: purple_bold + shape_binary: purple_bold + shape_block: blue_bold + shape_bool: light_cyan + shape_closure: green_bold + shape_custom: green + shape_datetime: cyan_bold + shape_directory: cyan + shape_external: cyan + shape_externalarg: green_bold + shape_external_resolved: light_purple_bold + shape_filepath: cyan + shape_flag: blue_bold + 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 + shape_keyword: cyan_bold + shape_list: cyan_bold + shape_literal: blue + shape_match_pattern: green + shape_matching_brackets: { attr: u } + shape_nothing: light_cyan + shape_operator: yellow + shape_or: purple_bold + shape_pipe: purple_bold + shape_range: yellow_bold + shape_record: cyan_bold + shape_redirection: purple_bold + shape_signature: green_bold + shape_string: green + shape_string_interpolation: cyan_bold + shape_table: blue_bold + shape_variable: purple + shape_vardecl: purple + shape_raw_string: light_purple + } +} \ No newline at end of file diff --git a/crates/nu-std/std/mod.nu b/crates/nu-std/std/mod.nu index 4d1e74e72d..89454ee1f3 100644 --- a/crates/nu-std/std/mod.nu +++ b/crates/nu-std/std/mod.nu @@ -14,6 +14,7 @@ export module std/iter export module std/log export module std/math export module std/xml +export module std/config # Load main dirs command and all subcommands export use std/dirs main diff --git a/crates/nu-utils/src/default_files/README.md b/crates/nu-utils/src/default_files/README.md new file mode 100644 index 0000000000..2269771bbd --- /dev/null +++ b/crates/nu-utils/src/default_files/README.md @@ -0,0 +1,82 @@ +# Nushell configuration files + +## `default_env.nu`: + +* The internal default environment variables (other than `$env.config`) that will be set during Nushell startup. +* Is loaded *before* the user's `env.nu`. +* Will be loaded during any startup where the user's `env.nu` is also loaded. For example: + * During normal startup with `nu` + * During a startup where the user specifies an alternative `env.nu` via `nu --env-config ` +* Likewise, is never loaded during a startup where the user's `env.nu` would not be loaded. For example: + * `nu -n/--no-config` + * `nu -c "ls"` + * `nu ` +* Is not commented - Comments are in `sample_env.nu`. +* Should be optimized for fastest load times. +* Can be introspected via `config env --default | nu-highlight` + +## `default_config.nu`: + +Counterpart to `default_env.nu`. + +* Contains any `$env.config` values that are not set via Rust defaults. +* Is loaded *after* the user's `env.nu`. +* Is loaded *before* the user's `config.nu`. +* Will be loaded during any startup where the user's `config.nu` is also loaded. For example: + * During normal startup with `nu` + * During a startup where the user specifies an alternative `config.nu` via `nu --config ` +* Likewise, is never loaded during a startup where the user's `config.nu` would not be loaded. For example: + * `nu -n/--no-config` + * `nu -c "ls"` + * `nu ` +* Is not commented - Comments are in `sample_config.nu`. +* Should be optimized for fastest load times. Whenever possible, values should be set via nu-protocol::config + * Exception: `color_config` values are currently set in this file so that user's can introspect the values + * TODO: Implement defaults for `color_config` in nu-protocol::config and remove from `default_config.nu` +* Can be introspected via `config nu --default | nu-highlight` +* An ideal `default_config.nu` (when all values are set via `nu-protocol::config`) will simply be: + ``` + $env.config = {} + ``` + +## `sample_env.nu` + +* A commented file documenting the most common environment variables that a user might configure in `env.nu` +* For convenient in-shell access +* Can be pretty-printed via `config env --sample | nu-highlight` +* Since this file is for documentation only, include actual Nushell code without comments so that it can be pretty-printed +* No optimization necessary - Not intended for use other than documentation. +* Consider replacing `config env --sample` with `help env.nu` at some point. +* Uses a mix of default values (explained) as well as other examples that users might want in their own `env.nu` + +## `sample_config.nu` + +Counterpart to `sample_env.nu`. + +TODO: **Not in final form** + +* A commented file documenting the most common environment variables that a user might configure in `config.nu` +* For convenient in-shell access +* Can be pretty-printed via `config nu --sample | nu-highlight` +* Since this file is for documentation only, include actual Nushell code without comments so that it can be pretty-printed +* No optimization necessary - Not intended for use other than documentation. +* Consider replacing `config nu --sample` with `help config.nu` at some point. +* Uses a mix of default values (explained) as well as other examples that users might want in their own `config.nu` + +## `scaffold_env.nu` + +* This file is used *one-time* (typically) at **first** startup +* If the `$nu.default-config-path` directory does not exist, the directory is created and then both `scaffold_env.nu` and `scaffold_config.nu` are written to it +* Contains only commented lines explaining the purpose of the file to the user, along with information on the `config env` command. + +## `scaffold_config.nu` + +Counterpart to `scaffold_env.nu`. + +* This file is used *one-time* (typically) at **first** startup +* If the `$nu.default-config-path` directory does not exist, the directory is created and then both `scaffold_env.nu` and `scaffold_config.nu` are written to it +* Contains only commented lines explaining the purpose of the file to the user, along with information on the `config nu` command. + +## `sample_login.nu` + +This file is not used by any Nushell code. Of course, if the user has a `login.nu`, then it will be evaluated during startup of a login shell. \ No newline at end of file diff --git a/crates/nu-utils/src/default_files/default_config.nu b/crates/nu-utils/src/default_files/default_config.nu new file mode 100644 index 0000000000..d95f4b3f97 --- /dev/null +++ b/crates/nu-utils/src/default_files/default_config.nu @@ -0,0 +1,63 @@ +# Nushell Config File +# +# version = "0.100.1" +$env.config.color_config = { + separator: white + leading_trailing_space_bg: { attr: n } + header: green_bold + empty: blue + bool: light_cyan + int: white + filesize: cyan + duration: white + date: purple + range: white + float: white + string: white + nothing: white + binary: white + cell-path: white + row_index: green_bold + record: white + list: white + block: white + hints: dark_gray + search_result: { bg: red fg: white } + shape_and: purple_bold + shape_binary: purple_bold + shape_block: blue_bold + shape_bool: light_cyan + shape_closure: green_bold + shape_custom: green + shape_datetime: cyan_bold + shape_directory: cyan + shape_external: cyan + shape_externalarg: green_bold + shape_external_resolved: light_yellow_bold + shape_filepath: cyan + shape_flag: blue_bold + shape_float: purple_bold + shape_glob_interpolation: cyan_bold + shape_globpattern: cyan_bold + shape_int: purple_bold + shape_internalcall: cyan_bold + shape_keyword: cyan_bold + shape_list: cyan_bold + shape_literal: blue + shape_match_pattern: green + shape_matching_brackets: { attr: u } + shape_nothing: light_cyan + shape_operator: yellow + shape_or: purple_bold + shape_pipe: purple_bold + shape_range: yellow_bold + shape_record: cyan_bold + shape_redirection: purple_bold + shape_signature: green_bold + shape_string: green + shape_string_interpolation: cyan_bold + shape_table: blue_bold + shape_variable: purple + shape_vardecl: purple + shape_raw_string: light_purple +} \ No newline at end of file diff --git a/crates/nu-utils/src/default_files/default_env.nu b/crates/nu-utils/src/default_files/default_env.nu new file mode 100644 index 0000000000..498c9670b3 --- /dev/null +++ b/crates/nu-utils/src/default_files/default_env.nu @@ -0,0 +1,57 @@ +# Default Nushell Environment Config File +# These "sensible defaults" are set before the user's `env.nu` is loaded +# +# version = "0.100.1" + +$env.PROMPT_COMMAND = {|| + let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { + null => $env.PWD + '' => '~' + $relative_pwd => ([~ $relative_pwd] | path join) + } + + let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold }) + let separator_color = (if (is-admin) { ansi light_red_bold } else { ansi light_green_bold }) + let path_segment = $"($path_color)($dir)(ansi reset)" + + $path_segment | str replace --all (char path_sep) $"($separator_color)(char path_sep)($path_color)" +} + +$env.PROMPT_INDICATOR = "> " +$env.PROMPT_INDICATOR_VI_NORMAL = "> " +$env.PROMPT_INDICATOR_VI_INSERT = ": " +$env.PROMPT_MULTILINE_INDICATOR = "::: " + +$env.PROMPT_COMMAND_RIGHT = {|| + # create a right prompt in magenta with green separators and am/pm underlined + let time_segment = ([ + (ansi reset) + (ansi magenta) + (date now | format date '%x %X') # try to respect user's locale + ] | str join | str replace --regex --all "([/:])" $"(ansi green)${1}(ansi magenta)" | + str replace --regex --all "([AP]M)" $"(ansi magenta_underline)${1}") + + let last_exit_code = if ($env.LAST_EXIT_CODE != 0) {([ + (ansi rb) + ($env.LAST_EXIT_CODE) + ] | str join) + } else { "" } + + ([$last_exit_code, (char space), $time_segment] | str join) +} + +$env.ENV_CONVERSIONS = { + "PATH": { + from_string: { |s| $s | split row (char esep) | path expand --no-symlink } + to_string: { |v| $v | path expand --no-symlink | str join (char esep) } + } +} + +$env.NU_LIB_DIRS = [ + ($nu.default-config-dir | path join 'scripts') # add /scripts + ($nu.data-dir | path join 'completions') # default home for nushell completions +] + +$env.NU_PLUGIN_DIRS = [ + ($nu.default-config-dir | path join 'plugins') # add /plugins +] \ No newline at end of file diff --git a/crates/nu-utils/src/default_files/sample_config.nu b/crates/nu-utils/src/default_files/sample_config.nu new file mode 100644 index 0000000000..5a8181506c --- /dev/null +++ b/crates/nu-utils/src/default_files/sample_config.nu @@ -0,0 +1,792 @@ +# Nushell Config File +# +# version = "0.99.2" +# +# A `config.nu` file is used to override default Nushell settings, +# define (or import) custom commands, or run any other startup tasks. +# See https://www.nushell.sh/book/configuration.html +# +# Nushell sets "sensible defaults" for most configuration settings, so +# the user's `config.nu` only needs to override these defaults if +# desired. +# +# This file serves as simple "in-shell" documentation for these +# settings, or you can view a more complete discussion online at: +# https://nushell.sh/book/configuration +# +# You can pretty-print and page this file using: +# config nu --sample | nu-highlight | less -R + +# $env.config +# ----------- +# The $env.config environment variable is a record containing most Nushell +# configuration settings. Keep in mind that, as a record, setting it to a +# new record will remove any keys which aren't in the new record. Nushell +# will then automatically merge in the internal defaults for missing keys. +# +# The same holds true for keys in the $env.config which are also records +# or lists. +# +# For this reason, settings are typically changed by updating the value of +# a particular key. Merging a new config record is also possible. See the +# Configuration chapter of the book for more information. + +# ------------------------ +# History-related settings +# ------------------------ +# $env.config.history.* + +# file_format (string): Either "sqlite" or "plaintext". While text-backed history +# is currently the default for historical reasons, "sqlite" is stable and +# provides more advanced history features. +$env.config.history.file_format = "sqlite" + +# max_size (int): The maximum number of entries allowed in the history. +# After exceeding this value, the oldest history items will be removed +# as new commands are added. +$env.config.history.max_size = 5_000_000 + +# sync_on_enter (bool): Whether the plaintext history file is updated +# each time a command is entered. If set to `false`, the plaintext history +# is only updated/written when the shell exits. This setting has no effect +# for SQLite-backed history. +$env.config.history.sync_on_enter = true + +# isolation (bool): +# `true`: New history from other currently-open Nushell sessions is not +# seen when scrolling through the history using PrevHistory (typically +# the Up key) or NextHistory (Down key) +# `false`: All commands entered in other Nushell sessions will be mixed with +# those from the current shell. +# Note: Older history items (from before the current shell was started) are +# always shown. +# This setting only applies to SQLite-backed history +$env.config.history.isolation = true + +# ---------------------- +# Miscellaneous Settings +# ---------------------- + +# show_banner (bool): Enable or disable the welcome banner at startup +$env.config.show_banner = true + +# rm.always_trash (bool): +# true: rm behaves as if the --trash/-t option is specified +# false: rm behaves as if the --permanent/-p option is specified (default) +# Explicitly calling `rm` with `--trash` or `--permanent` always override this setting +# Note that this feature is dependent on the host OS trashcan support. +$env.config.rm.always_trash = false + +# recursion_limit (int): how many times a command can call itself recursively +# before an error will be generated. +$env.config.recursion_limit = 50 + +# --------------------------- +# Commandline Editor Settings +# --------------------------- + +# edit_mode (string) "vi" or "emacs" sets the editing behavior of Reedline +edit_mode: "emacs" + +# Command that will be used to edit the current line buffer with Ctrl+O. +# If unset, uses $env.VISUAL and then $env.EDITOR +# +# Tip: Set to "editor" to use the default editor on Unix platforms using +# the Alternatives system or equivalent +buffer_editor: "editor" + +# cursor_shape_* (string) +# ----------------------- +# The following variables accept a string from the following selections: +# "block", "underscore", "line", "blink_block", "blink_underscore", "blink_line", or "inherit" +# "inherit" skips setting cursor shape and uses the current terminal setting. +$env.config.cursor_shape.emacs = "inherit" # Cursor shape in emacs mode +$env.config.cursor_shape.vi_insert = "block" # Cursor shape in vi-insert mode +$env.config.cursor_shape.vi_normal = "underscore" # Cursor shape in normal vi mode + +# -------------------- +# Completions Behavior +# -------------------- +# $env.config.completions.* +# Apply to the Nushell completion system + +# algorithm (string): Either "prefix" or "fuzzy" +$env.config.completions.algorithm = "prefix" + +# sort (string): One of "smart" or "alphabetical" +# In "smart" mode sort order is based on the "algorithm" setting. +# When using the "prefix" algorithm, results are alphabetically sorted. +# When using the "fuzzy" algorithm, results are sorted based on their fuzzy score. +$env.config.completions.sort = "smart" + +# case_sensitive (bool): true/false to enable/disable case-sensitive completions +$env.config.completions.case_sensitive = false + +# quick (bool): +# true: auto-select the completion when only one remains +# false: prevents auto-select of the final result +$env.config.completions.quick = true + +# partial (bool): +# true: Partially complete up to the best possible match +# false: Do not partially complete +# Partial Example: If a directory contains only files named "forage", "food", and "forest", +# then typing "ls " and pressing will partially complete the first two +# letters, "f" and "o". If the directory also includes a file named "faster", +# then only "f" would be partially completed. +$env.config.completions.partial = true + +# use_ls_colors (bool): When true, apply LS_COLORS to file/path/directory matches +$env.config.completions.use_ls_colors = true + +# -------------------- +# External Completions +# -------------------- +# completions.external.*: Settings related to completing external commands +# and additional completers + +# external.exnable (bool) +# true: search for external commands on the Path +# false: disabling might be desired for performance if your path includes +# directories on a slower filesystem +$env.config.completions.external.enable = true + +# max_results (int): Limit the number of external commands retrieved from +# path to this value. Has no effect if `...external.enable` (above) is set to `false` +$env.config.completions.external.max_results = 50 + +# completer (closure with a |spans| parameter): A command to call for *argument* completions +# to commands (internal or external). +# +# The |spans| parameter is a list of strings representing the tokens (spans) +# on the current commandline. It is always a list of at least two strings - The +# command being completed plus the first argument of that command ("" if no argument has +# been partially typed yet), and additional strings for additional arguments beyond +# the first. +# +# This setting is usually set to a closure which will call a third-party completion system, such +# as Carapace. +# +# Note: The following is an over-simplified completer command that will call Carapace if it +# is installed. Please use the official Carapace completer, which can be generated automatically +# by Carapace itself. See the Carapace documentation for the proper syntax. +$env.config.completions.external.completer = {|spans| + carapace $spans.0 nushell ...$spans | from json +} + +# -------------------- +# Terminal Integration +# -------------------- +# Nushell can output a number of escape codes to enable advanced features in Terminal Emulators +# that support them. Settings in this section enable or disable these features in Nushell. +# Features aren't supported by your Terminal can be disabled. Features can also be disabled, +# of course, if there is a conflict between the Nushell and Terminal's implementation. + +# use_kitty_protocol (bool): +# A keyboard enhancement protocol supported by the Kitty Terminal. Additional keybindings are +# available when using this protocol in a supported terminal. For example, without this protocol, +# Ctrl+I is interpreted as the Tab Key. With this protocol, Ctrl+I and Tab can be mapped separately. +$env.config.use_kitty_protocol = false + +# osc2 (bool): +# When true, the current directory and running command are shown in the terminal tab/window title. +# Also abbreviates the directory name by prepending ~ to the home directory and its subdirectories. +$env.config.shell_integration.osc2 = true + +# osc7 (bool): +# Nushell will report the current directory to the terminal using OSC 7. This is useful when +# spawning new tabs in the same directory. +$env.config.shell_integration.osc7 = true + +# osc9_9 (bool): +# Enables/Disables OSC 9;9 support, originally a ConEmu terminal feature. This is an +# alternative to OSC 7 which also communicates the current path to the terminal. +$env.config.shell_integration.osc9_9 = false + +# osc8 (bool): +# When true, the `ls` command will generate clickable links that can be launched in another +# application by the terminal. +# Note: This setting replaces the now deprecated `ls.show_clickable_links` +$env.config.shell.integration.osc8: true + +# Deprecated +# $env.config.ls.clickable_links = true + +# osc133 (bool): +# true/false to enable/disable OSC 133 support, a set of several escape sequences which +# report the (1) starting location of the prompt, (2) ending location of the prompt, +# (3) starting location of the command output, and (4) the exit code of the command. + +# originating with Final Term. These sequences report information regarding the prompt +# location as well as command status to the terminal. This enables advanced features in +# some terminals, including the ability to provide separate background colors for the +# command vs. the output, collapsible output, or keybindings to scroll between prompts. +$env.config.shell_integration.osc133 = true + +# osc633 (bool): +# true/false to enable/disable OSC 633, an extension to OSC 133 for Visual Studio Code +$env.config.shell_integration.osc633 = true + +# reset_application_mode (bool): +# true/false to enable/disable sending ESC[?1l to the terminal +# This sequence is commonly used to keep cursor key modes in sync between the local +# terminal and a remove SSH host. +$env.config.shell_integration.reset_application_mode = true + +# bracketed_paste (bool): +# true/false to enable/disable the bracketed-paste feature, which allows multiple-lines +# to be pasted into Nushell at once without immediate execution. When disabled, +# each pasted line is executed as it is received. +# Note that bracketed paste is not currently supported on the Windows version of +# Nushell. +$env.config.bracketed_paste = true + +# use_ansi_coloring (bool): +# true/false to enable/disable the use of ANSI colors in Nushell internal commands. +# When disabled, output from Nushell built-in commands will display only in the default +# foreground color. +# Note: Does not apply to the `ansi` command. +$env.config.use_ansi_coloring = true + +# ---------------------- +# Error Display Settings +# ---------------------- + +# error_style (string): One of "fancy" or "plain" +# Plain: Display plain-text errors for screen-readers +# Fancy: Display errors using line-drawing characters to point to the span in which the +# problem occurred. +$env.config.error_style = "fancy" + +# display_errors.exit_code (bool): +# true: Display a Nushell error when an external command returns a non-zero exit code +# false: Display only the error information printed by the external command itself +# Note: Core dump errors are always printed; SIGPIPE never triggers an error +$env.config.display_errors.exit_code = false + +# display_errors.termination_signal (bool): +# true/false to enable/disable displaying a Nushell error when a child process is +# terminated via any signal +$env.config.display_errors.termination_signal = true + +# ------------- +# Table Display +# ------------- +# footer_mode (string or int): +# Specifies when to display table footers with column names. Allowed values: +# "always" +# "never" +# "auto": When the length of the table would scroll the header past the first line of the terminal +# (int): When the number of table rows meets or exceeds this value +# Note: Does not take into account rows with multiple lines themselves +$env.config.footer_mode = 25 + +# table.* +# table_mode (string): +# One of: "default", "basic", "compact", "compact_double", "heavy", "light", "none", "reinforced", +# "rounded", "thin", "with_love", "psql", "markdown", "dots", "restructured", "ascii_rounded", +# or "basic_compact" +# Can be overridden by passing a table to `| table --theme/-t` +$env.config.table.mode = "default" + +# index_mode (string) - One of: +# "never": never show the index column in a table or list +# "always": always show the index column in tables and lists +# "auto": show the column only when there is an explicit "index" column in the table +# Can be overridden by passing a table to `| table --index/-i` +$env.config.table.index_mode = "always" + +# show_empty (bool): +# true: show "empty list" or "empty table" when no values exist +# false: display no output when no values exist +$env.config.table.show_empty = true + +# padding.left/right (int): The number of spaces to pad around values in each column +$env.config.table.padding.left = 1 +$env.config.table.padding.right = 1 + +# trim.*: The rules that will be used to display content in a table row when it would cause the +# table to exceed the terminal width. +# methodology (string): One of "wrapping" or "truncating" +# truncating_suffix (string): The text to show at the end of the row to indicate that it has +# been truncated. Only valid when `methodology = "truncating"`. +# wrapping_try_keep_words (bool): true to keep words together based on whitespace +# false to allow wrapping in the middle of a word. +# Only valid when `methodology = wrapping`. +$env.config.table.trim = { + methodology: "wrapping" + wrapping_try_keep_words: true +} +# or +$env.config.table.trim = { + methodology: "truncating" + truncating_suffix: "..." +} + +# header_on_separator (bool): +# true: Displays the column headers as part of the top (or bottom) border of the table +# false: Displays the column header in its own row with a separator below. +$env.config.table.header_on_separator = false + +# abbreviated_row_count (int or nothing): +# If set to an int, all tables will be abbreviated to only show the first and last rows +# If set to `null`, all table rows will be displayed +# Can be overridden by passing a table to `| table --abbreviated/-a` +$env.config.table.abbreviated_row_count + +# footer_inheritance (bool): Footer behavior in nested tables +# true: If a nested table is long enough on its own to display a footer (per `footer_mode` above), +# then also display the footer for the parent table +# false: Always apply `footer_mode` rules to the parent table +$env.config.table.footer_inheritance = false + +# ---------------- +# Datetime Display +# ---------------- +# datetime_format.* (string or nothing): +# Format strings that will be used for datetime values. +# When set to `null`, the default behavior is to "humanize" the value (e.g., "now" or "a day ago") + +# datetime_format.table (string or nothing): +# The format string (or `null`) that will be used to display a datetime value when it appears in a +# structured value such as a table, list, or record. +$env.config.datetime_format.table = null + +# datetime_format.normal (string or nothing): +# The format string (or `null`) that will be used to display a datetime value when it appears as +# a raw value. +$env.config.datetime_format.normal = "%m/%d/%y %I:%M:%S%p" + +# ---------------- +# Filesize Display +# ---------------- +# filesize.metric (bool): When displaying filesize values ... +# true: Use the ISO-standard KB, MB, GB +# false: Use the Windows-standard KiB, MiB, GiB +$env.config.filesize.metric = false + +# filesize.format (string): One of either: +# - The filesize units such as "KB", "KiB", etc. In this case, filesize values always display using +# this unit. +# - Or "auto": Filesizes are displayed using the closest unit. For example, 1_000_000_000b will display +# as 953.7 MiB (when `metric = false`) or 1.0GB (when `metric = true`) +$env.config.filesize.format = "auto" + +# --------------------- +# Miscellaneous Display +# --------------------- + +# render_right_prompt_on_last_line(bool): +# true: When using a multi-line left-prompt, the right-prompt will be displayed on the last line +# false: The right-prompt is displayed on the first line of the left-prompt +$env.config.render_right_prompt_on_last_line = false + +# float_precision (int): +# Float values will be rounded to this precision when displaying in structured values such as lists, +# tables, or records. +$env.config.float_precision = 2 + +# ls.use_ls_colors (bool): +# true: The `ls` command will apply the $env.LS_COLORS standard to filenames +# false: Filenames in the `ls` table will use the color_config for strings +$env.config.ls = true + +# Hooks +# ----- +# $env.config.hooks is a record containing the five different types of Nushell hooks. +# See the Hooks documentation at https://www.nushell.sh/book/hooks for details +# +# Most hooks can accept a string, a closure, or a list containing strings and/or closures. +# The display_output record can only accept a string or a closure, but never a list +# +# WARNING: A malformed display_output hook can suppress all Nushell output to the terminal. +# It can be reset by assigning an empty string as below: + +$env.config.hooks.pre_prompt = [] # Before each prompt is displayed +$env.config.hooks.pre_execution = [] # After is pressed; before the commandline + # is executed +$env.config.hooks.env_change = [] # When a specified environment variable changes +$env.config.hooks.display_output = "" # Before Nushell output is displayed in the terminal +$env.config.hooks.command_not_found = [] # When a command is not found + +# ----------- +# Keybindings +# ----------- +# keybindings (list): A list of user-defined keybindings +# Nushell/Reedline keybindings can be added or overridden using this setting. +# See https://www.nushell.sh/book/line_editor.html#keybindings for details. +# +# Example - Add a new Alt+. keybinding to insert the last token used on the previous commandline +$env.config.keybindings ++= [ + { + name: insert_last_token + modifier: alt + keycode: char_. + mode: [emacs vi_normal vi_insert] + event: [ + { edit: InsertString, value: "!$" } + { send: Enter } + ] + } +] + +# Example: Override the F1 keybinding with a user-defined help menu (see "Menus" below): +$env.config.keybindings ++= [ + { + name: help_menu + modifier: none + keycode: f1 + mode: [emacs, vi_insert, vi_normal] + event: { send: menu name: help_menu } + } +] + +# ----- +# Menus +# ----- +# menus (list): +# +# Nushell/Reedline menus can be created and modified using this setting. +# See https://www.nushell.sh/book/line_editor.html#menus for details. +# +# Note that menus are usually activated via keybindings, which are defined in +# $env.config.keybindings (above). +# +# Simple example - Add a new Help menu to the list (note that a similar menu is already +# defined internally): +$env.config.menus ++= [ + { + name: help_menu + only_buffer_difference: true + marker: "? " + type: { + layout: description + columns: 4 + col_width: 20 # Optional value. If missing all the screen width is used to calculate column width + col_padding: 2 + selection_rows: 4 + description_rows: 10 + } + style: { + text: green + selected_text: green_reverse + description_text: yellow + } + } +] + + +# --------------- +# Plugin behavior +# --------------- +# Per-plugin configuration. See https://www.nushell.sh/contributor-book/plugins.html#configuration. +plugins: {} +$env.config.plugins +$env.config.plugin_gc +$env.config.plugin_gc.default +$env.config.plugin_gc.default.enabled +$env.config.plugin_gc.default.stop_after +$env.config.plugin_gc.plugins + plugin_gc: { + # Configuration for plugin garbage collection + default: { + enabled: true # true to enable stopping of inactive plugins + stop_after: 10sec # how long to wait after a plugin is inactive to stop it + } + plugins: { + # alternate configuration for specific plugins, by name, for example: + # + # gstat: { + # enabled: false + # } + } + } + + +# ------------------------------------- +# Themes/Colors and Syntax Highlighting +# ------------------------------------- +# For more information on defining custom themes, see +# https://www.nushell.sh/book/coloring_and_theming.html + +# Use and/or contribute to the theme collection at +# https://github.com/nushell/nu_scripts/tree/main/themes + +# Values: + +# highlight_resolved_externals (bool): +# true: Applies the `color_config.shape_external_resolved` color (below) to external commands +# which are found (resolved) on the path +# false: Applies the `color_config.shape_external` color to *all* externals simply based on whether +# or not they would be *parsed* as an external command based on their position. +# Defaults to false for systems with a slower search path +$env.config.highlight_resolved_externals = true + +# color_config (record): A record of shapes, types, UI elements, etc. that can be styled (e.g., +# colorized) in Nushell, either on the commandline itself (shapes) or in output. +# +# Note that this is usually set through a theme provided by a record in a custom command. For +# instance, the standard library contains two "starter" theme commands: "dark-theme" and +# "light-theme". For example: +use std/config dark-theme +$env.config.color_config = (dark-theme) + +# Or, individual color settings can be configured or overridden. +# +# Values can be one of: +# - A color name such as "red" (see `ansi -l` for a list) +# - A color RGB value in the form of "#C4C9C6" +# - A record including: +# * `fg` (color) +# * `bg` (color) +# * `attr`: a string with one or more of: +# - 'n': normal +# - 'b': bold +# - 'u': underline +# - 'r': reverse +# - 'i': italics +# - 'd': dimmed + +# foreground, background, and cursor colors are not handled by Nushell, but can be used by +# custom-commands such as `theme` from the nu_scripts repository. That `theme` command can be +# used to set the terminal foreground, background, and cursor colors. +$env.config.color_config.foreground +$env.config.color_config.background +$env.config.color_config.cursor + +# ------------------------------------------------------------------------------------------------- +# shape_: Applies syntax highlighting based on the "shape" (inferred or declared type) of an +# element on the commandline. Nushell's parser can identify shapes based on many criteria, often +# as the commandline is being typed. + +# shape_string: Can appear as a single-or-quoted value, a bareword string, the key of a record, +# an argument which has been declared as a string, and other parsed strings. +$env.config.color_config.shape_string + +# shape_string_interpolation: A single-or-double-quoted string interpolation. This style +# applies to the dollar sign and quotes of the string. The elements inside the string are +# styled according to their own shape. +$env.config.color_config.shape_string_interpolation + +# shape_raw_string: a raw string literal. E.g., r#'This is a raw string'#. This style applies +# to the entire raw string. +$env.config.color_config.shape_raw_string + +# shape_record: A record-literal. This style applies to the brackets around the record. The keys +# and values will be styled according to their individual shapes. +$env.config.color_config.shape_record + +# shape_list: A list-literal. This style applies to the brackets and list separator only. The +# items in a list are styled according to their individual shapes. +$env.config.color_config.shape_list + +# shape_table: A table-literl. Color applies to the brackets, semicolon, and list separators. The +# items in the table are style according to their individual shapes. +$env.config.color_config.shape_table + +# shape_bool: A boolean-literal `true` or `false` value +$env.config.color_config.shape_bool + +# shape_int: Integer literals +$env.config.color_config.shape_int + +# shape_float: Float literals. E.g., 5.4 +# Also integer literals in a float-argument position +$env.config.color_config.shape_float + +# shape_range: Range literals +$env.config.color_config.shape_range + +# shape_binary: Binary literals +$env.config.color_config.shape_binary + +# shape_datetime: Datetime literals +$env.config.color_config.shape_datetime + +# shape_custom: A custom value, usually from a plugin +$env.config.color_config.shape_custom + +# shape_nothing: A literal `null` +$env.config.color_config.shape_nothing + +# shape_literal: Not currently used +$env.config.color_config.shape_literal + +# shape_operator: An operator such as +, -, ++, in, not-in, etc. +$env.config.color_config.shape_operator + +# shape_filepath: An argument that appears in the position of a `path` shape for a command +$env.config.color_config.shape_filepath + +# shape_directory: A more specific 'path' shape that only accepts a directory. +$env.config.color_config.shape_directory + +# shape_globpattern: An argument in the position of a glob parameter. E.g., the asterisk (or any other string) in `ls *`. +$env.config.color_config.shape_globpattern + +# shape_glob_interpolation: Deprecated +$env.config.color_config.shape_glob_interpolation + +# shape_garbage: When an argument is of the wrong type or cannot otherwise be parsed. +# E.g., `ls {a: 5}` - A record argument to `ls` is 'garbage'. Also applied in real-time when +# an expression is not (yet) properly closed. +$env.config.color_config.shape_garbage + +# shape_or and shape_and: The and and or operators. +# Note: Not currently implemented. +$env.config.color_config.shape_or +$env.config.color_config.shape_and + +# shape_variable: The *use* of a variable. E.g., `$env` or `$a`. +$env.config.color_config.shape_variable + +# shape_vardecl: The *declaration* of a variable. E.g. the "a" in `let a = 5`. +$env.config.color_config.shape_vardecl + +# shape_matching_brackets: When the cursor is positioned on an opening or closing bracket (e.g, +# braces, curly braces, or parenthesis), and there is a matching opening/closing bracket, both will +# temporarily have this style applied. +$env.config.color_config.shape_matching_brackets + +# shape_pipe: The pipe `|` when used to separate expressions in a pipeline +$env.config.color_config.shape_pipe + +# shape_internalcall: A known Nushell built-in or custom command in the "command position" (usually +# the first bare word of an expression). +$env.config.color_config.shape_internalcall + +# shape_external: A token in the "command position" (see above) that is not a known Nushell +# built-in or custom command. This is assumed to be an external command. +$env.config.color_config.shape_external + +# shape_external_resolved: Requires "highlight_resolved_externals" (above) to be enabled. +# When a token matches the "external" requirement (above) and is also a *confirmed* external +# command, this style will be applied. +$env.config.color_config.shape_external_resolved + +# shape_externalarg: Arguments to an external command (whether resolved or not) +$env.config.color_config.shape_externalarg + +# shape_match_pattern: The matching pattern for each arm in a match expression. Does not +# include the guard expression (if present). +$env.config.color_config.shape_match_pattern + +# shape_block: The curly-braces around a block. Expressions within the block will have their +# their own shapes' styles applied. +$env.config.color_config.shape_block + +# shape_signature: The parameter definitions and input/output types for a command signature. +$env.config.color_config.shape_signature + +# shape_keyword: Not current used +$env.config.color_config.shape_keyword + +# shape_closure: Styles the brackets and arguments of a closure. +$env.config.color_config.shape_closure + +# shape_direction: The redirection symbols such as `o>`, `error>`, `e>|`, etc. +$env.config.color_config.shape_redirection + +# shape_flag: Flags and switches to internal and custom-commands. Only the `--flag` (`-f`) portion +# is styled. The argument to a flag will be styled using its own shape. +$env.config.color_config.shape_flag + +# ------------------------------------------------------------------------------------------------- +# color.config. +# *Values* of a particular *type* can be styled differently than the *shape*. +# Note that the style is applied only when this type is displayed in *structured* data (list, +# record, or table). It is not currently applied to basic raw values. +# +# Note that some types are rarely or never seen in a context in which styling would be applied. +# For example, a cell-path *value* is unlikely to (but can) appear in a list, record, or table. +# +# Tip: In addition to the styles above (fg, bg, attr), types typically accept a closure which can +# dynamically change the style based on the *value*. For instance, the themes in the nu_scripts +# repository will style filesizes difference in an `ls` (or other table) differently depending on +# their magnitude. + +# Simple examples: + +# bool: A boolean value +$env.config.color_config.bool = {|| + if $in { + { + bg: 'light_green' + fg: 'white' + attr: 'b' + } + } else { + { + bg: 'yellow' + fg: 'black' + attr: 'b' + } + } +} + +# int: An integer value +$env.config.color_config.int = {|| + if $in == 42 { 'green' } else { 'red' } +} + +# Additional type values (without examples): +$env.config.color_config.string # String +$env.config.color_config.float # Float value +$env.config.color_config.glob # Glob value (must be declared) +$env.config.color_config.binary # Binary value +$env.config.color_config.custom # Custom value (often from a plugin) +$env.config.color_config.nothing # Not used, since a null is not displayed +$env.config.color_config.date # datetime value +$env.config.color_config.filesize # filesize value +$env.config.color_config.list # Not currently used. Lists are displayed using their + # members' styles +$env.config.color_config.record # Not currently used. Records are displayed using their + # member's styles +$env.config.color_config.duration # Duration type +$env.config.color_config.range # Range value +$env.config.color_config.cell-path # Cell-path value +$env.config.color_config.closure # Not currently used +$env.config.color_config.block # Not currently used + +# Additional UI elements +# hints: The (usually dimmed) style in which completion hints are displayed +$env.config.color_config.hints + +# search_result: The style applied to `find` search results +$env.config.color_config.search_result + +# header: The column names in a table header +$env.config.color_config.header + +# separator: Used for table/list/record borders +$env.config.color_config.separator + +# row_index: The `#` or `index` column of a table or list +$env.config.color_config.row_index + +# empty: This style is applied to empty/missing values in a table. However, since the ❎ +# emoji is used for this purpose, there is limited styling that can be applied. +$env.config.color_config.empty + +# leading_trailing_space_bg: When a string value inside structured data has leading or trailing +# whitespace, that whitespace will be displayed using this style. +# Use { attr: n } to disable. +$env.config.color_config.leading_trailing_space_bg = { bg: 'red' } + +# ------------------------ +# `explore` command colors +# ------------------------ +# Configure the UI colors of the `explore` command +# Allowed values are the same as for the `color_config` options above. +# Example: +$env.config.explore = { + status_bar_background: { fg: "#1D1F21", bg: "#C4C9C6" }, + command_bar_text: { fg: "#C4C9C6" }, + highlight: { fg: "black", bg: "yellow" }, + status: { + error: { fg: "white", bg: "red" }, + warn: {} + info: {} + }, + selected_cell: { bg: light_blue }, +} diff --git a/crates/nu-utils/src/default_files/sample_env.nu b/crates/nu-utils/src/default_files/sample_env.nu new file mode 100644 index 0000000000..84926abb2f --- /dev/null +++ b/crates/nu-utils/src/default_files/sample_env.nu @@ -0,0 +1,135 @@ +# Sample Nushell Environment Config File +# +# Environment variables are usually configured in `env.nu`. Nushell +# sets sensible defaults for many environment variables, so the user's +# `env.nu` only needs to override these defaults if desired. +# +# This file serves as simple "in-shell" documentation for these +# settings, or you can view a more complete discussion online at: +# https://nushell.sh/book/configuration +# +# You can pretty-print and page this file using: +# config env --sample | nu-highlight | less -R + +# PROMPT_* +# -------- +# Prompt configuration +# PROMPT_ variables accept either a string or a closure that returns a string + +# PROMPT_COMMAND +# -------------- +# Defines the primary prompt. Note that the PROMPT_INDICATOR (below) is appended to this value. +# Simple example - Static string: +$env.PROMPT_COMMAND = "Nushell" +# Simple example - Dynamic closure displaying the path: +$env.PROMPT_COMMAND = {|| pwd} + +# PROMPT_INDICATOR* +# ----------------- +# The prompt indicators are environmental variables that represent +# the state of the prompt. The specified character(s) will appear +# immediately following the PROMPT_COMMAND + +# When in Emacs mode (default): +$env.PROMPT_INDICATOR = "> " + +# When in normal vi mode: +$env.PROMPT_INDICATOR_VI_NORMAL = "> " +# When in vi insert-mode: +$env.PROMPT_INDICATOR_VI_INSERT = ": " + +# When a commandline extends across multiple lines: +$env.PROMPT_MULTILINE_INDICATOR = "::: " + +# TRANSIENT_PROMPT_* +# ------------------ +# Allows a different prompt to be shown after a command has been executed. This +# can be useful if you have a 2-line prompt. Instead of each previously-entered +# command taking up at least 2 lines, the transient prompt can condense it to a +# shorter version. The following example shows a rocket emoji before each +# previously-entered command: +$env.TRANSIENT_PROMPT_COMMAND = "🚀 " +$env.TRANSIENT_PROMPT_INDICATOR = "" +$env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = "" +$env.TRANSIENT_PROMPT_INDICATOR_VI_NORMAL = "" +# Tip: Removing the transient multiline indicator and right-prompt can simplify +# copying from the terminal +$env.TRANSIENT_PROMPT_MULTILINE_INDICATOR = "" +$env.TRANSIENT_PROMPT_COMMAND_RIGHT = "" + +# ENV_CONVERSIONS +# --------------- +# Certain variables, such as those containing multiple paths, are often stored as a +# colon-separated string in other shells. Nushell can convert these automatically to a +# more convenient Nushell list. The ENV_CONVERSIONS variable specifies how environment +# variables are: +# - converted from a string to a value on Nushell startup (from_string) +# - converted from a value back to a string when running external commands (to_string) +# +# Note: The OS Path variable is automatically converted before env.nu loads, so it can +# be treated a list in this file. +# +# Note: Environment variables are not case-sensitive, so the following will work +# for both Windows and Unix-like platforms. +# +# By default, the internal conversion looks something like the following, so there +# is no need to add this in your actual env.nu: +$env.ENV_CONVERSIONS = { + "Path": { + from_string: { |s| $s | split row (char esep) | path expand --no-symlink } + to_string: { |v| $v | path expand --no-symlink | str join (char esep) } + } +} + +# Here's an example converts the XDG_DATA_DIRS variable to and from a list: +$env.ENV_CONVERSIONS = $env.ENV_CONVERSIONS | merge { + "XDG_DATA_DIRS": { + from_string: { |s| $s | split row (char esep) | path expand --no-symlink } + to_string: { |v| $v | path expand --no-symlink | str join (char esep) } + } +} +# +# Other common directory-lists for conversion: TERMINFO_DIRS. +# Note that other variable conversions take place after `config.nu` is loaded. + +# NU_LIB_DIRS +# ----------- +# Directories in this environment variable are searched by the +# `use` and `source` commands. +# +# By default, the `scripts` subdirectory of the default configuration +# directory is included: +$env.NU_LIB_DIRS = [ + ($nu.default-config-dir | path join 'scripts') # add /scripts + ($nu.data-dir | path join 'completions') # default home for nushell completions +] +# You can replace (override) or append to this list: +$env.NU_LIB_DIRS ++= ($nu.default-config-dir | path join 'modules') + +# NU_PLUGIN_DIRS +# -------------- +# Directories to search for plugin binaries when calling register. + +# By default, the `plugins` subdirectory of the default configuration +# directory is included: +$env.NU_PLUGIN_DIRS = [ + ($nu.default-config-dir | path join 'plugins') # add /plugins +] + +# Appending to the OS path is a common configuration task. +# Because of the previous ENV_CONVERSIONS (performed internally +# before your env.nu loads), the path variable is a list that can +# be appended to using, for example: +$env.path ++= "~/.local/bin" + +# Or prepend using +$env.path = "~/.local/bin" ++ $env.path + +# The `path add` function from the Standard Library also provides +# a convenience method for prepending to the path: +use std/util "path add" +path add "~/.local/bin" +path add ($env.CARGO_HOME | path join "bin") + +# You can remove duplicate directories from the path using: +$env.PATH = ($env.PATH | uniq) diff --git a/crates/nu-utils/src/sample_config/sample_login.nu b/crates/nu-utils/src/default_files/sample_login.nu similarity index 100% rename from crates/nu-utils/src/sample_config/sample_login.nu rename to crates/nu-utils/src/default_files/sample_login.nu diff --git a/crates/nu-utils/src/default_files/scaffold_config.nu b/crates/nu-utils/src/default_files/scaffold_config.nu new file mode 100644 index 0000000000..7bf7ecc632 --- /dev/null +++ b/crates/nu-utils/src/default_files/scaffold_config.nu @@ -0,0 +1,19 @@ +# config.nu +# +# This file is used to override default Nushell settings, define +# (or import) custom commands, or run any other startup tasks. +# See https://www.nushell.sh/book/configuration.html +# +# This file is loaded after env.nu and before login.nu +# +# You can open this file in your default editor using: +# config nu +# +# To pretty-print a sample config.nu with documentation, run: +# config nu --sample | nu-highlight | less -R +# +# To pretty-print the default configuration values, run: +# config nu --default | nu-highlight | less -R +# +# You can remove these comments if you want or leave +# them for future reference. diff --git a/crates/nu-utils/src/default_files/scaffold_env.nu b/crates/nu-utils/src/default_files/scaffold_env.nu new file mode 100644 index 0000000000..a3ce3cab07 --- /dev/null +++ b/crates/nu-utils/src/default_files/scaffold_env.nu @@ -0,0 +1,18 @@ +# env.nu +# +# This file is typically used to add or override environment variables. +# See https://www.nushell.sh/book/configuration.html +# +# This file is loaded before config.nu and login.nu +# +# You can open this file in your default editor using: +# config env +# +# To pretty-print a sample env.nu with documentation, run: +# config env --sample | nu-highlight | less -R +# +# To pretty-print the default environment values, run: +# config env --default | nu-highlight | less -R +# +# You can remove these comments if you want or leave +# them for future reference. diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index 4b91d923db..413b5f8893 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -10,8 +10,9 @@ pub mod utils; pub use locale::get_system_locale; pub use utils::{ - enable_vt_processing, get_default_config, get_default_env, get_ls_colors, - stderr_write_all_and_flush, stdout_write_all_and_flush, + enable_vt_processing, get_default_config, get_default_env, get_ls_colors, get_sample_config, + get_sample_env, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush, + stdout_write_all_and_flush, }; pub use casing::IgnoreCaseExt; diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu deleted file mode 100644 index c8b398b684..0000000000 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ /dev/null @@ -1,899 +0,0 @@ -# Nushell Config File -# -# version = "0.100.1" - -# For more information on defining custom themes, see -# https://www.nushell.sh/book/coloring_and_theming.html -# And here is the theme collection -# https://github.com/nushell/nu_scripts/tree/main/themes -let dark_theme = { - # color for nushell primitives - separator: white - leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off - header: green_bold - empty: blue - # Closures can be used to choose colors for specific values. - # The value (in this case, a bool) is piped into the closure. - # eg) {|| if $in { 'light_cyan' } else { 'light_gray' } } - bool: light_cyan - int: white - filesize: cyan - duration: white - date: purple - range: white - float: white - string: white - nothing: white - binary: white - cell-path: white - row_index: green_bold - record: white - list: white - block: white - hints: dark_gray - search_result: { bg: red fg: white } - shape_and: purple_bold - shape_binary: purple_bold - shape_block: blue_bold - shape_bool: light_cyan - shape_closure: green_bold - shape_custom: green - shape_datetime: cyan_bold - shape_directory: cyan - shape_external: cyan - shape_externalarg: green_bold - shape_external_resolved: light_yellow_bold - shape_filepath: cyan - shape_flag: blue_bold - 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 - shape_keyword: cyan_bold - shape_list: cyan_bold - shape_literal: blue - shape_match_pattern: green - shape_matching_brackets: { attr: u } - shape_nothing: light_cyan - shape_operator: yellow - shape_or: purple_bold - shape_pipe: purple_bold - shape_range: yellow_bold - shape_record: cyan_bold - shape_redirection: purple_bold - shape_signature: green_bold - shape_string: green - shape_string_interpolation: cyan_bold - shape_table: blue_bold - shape_variable: purple - shape_vardecl: purple - shape_raw_string: light_purple -} - -let light_theme = { - # color for nushell primitives - separator: dark_gray - leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off - header: green_bold - empty: blue - # Closures can be used to choose colors for specific values. - # The value (in this case, a bool) is piped into the closure. - # eg) {|| if $in { 'dark_cyan' } else { 'dark_gray' } } - bool: dark_cyan - int: dark_gray - filesize: cyan_bold - duration: dark_gray - date: purple - range: dark_gray - float: dark_gray - string: dark_gray - nothing: dark_gray - binary: dark_gray - cell-path: dark_gray - row_index: green_bold - record: dark_gray - list: dark_gray - block: dark_gray - hints: dark_gray - search_result: { fg: white bg: red } - shape_and: purple_bold - shape_binary: purple_bold - shape_block: blue_bold - shape_bool: light_cyan - shape_closure: green_bold - shape_custom: green - shape_datetime: cyan_bold - shape_directory: cyan - shape_external: cyan - shape_externalarg: green_bold - shape_external_resolved: light_purple_bold - shape_filepath: cyan - shape_flag: blue_bold - 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 - shape_keyword: cyan_bold - shape_list: cyan_bold - shape_literal: blue - shape_match_pattern: green - shape_matching_brackets: { attr: u } - shape_nothing: light_cyan - shape_operator: yellow - shape_or: purple_bold - shape_pipe: purple_bold - shape_range: yellow_bold - shape_record: cyan_bold - shape_redirection: purple_bold - shape_signature: green_bold - shape_string: green - shape_string_interpolation: cyan_bold - shape_table: blue_bold - shape_variable: purple - shape_vardecl: purple - shape_raw_string: light_purple -} - -# External completer example -# let carapace_completer = {|spans| -# carapace $spans.0 nushell ...$spans | from json -# } - -# The default config record. This is where much of your global configuration is setup. -$env.config = { - show_banner: true # true or false to enable or disable the welcome banner at startup - - ls: { - use_ls_colors: true # use the LS_COLORS environment variable to colorize output - clickable_links: true # enable or disable clickable links. Your terminal has to support links. - } - - rm: { - always_trash: false # always act as if -t was given. Can be overridden with -p - } - - table: { - mode: rounded # basic, compact, compact_double, light, thin, with_love, rounded, reinforced, heavy, none, other - index_mode: always # "always" show indexes, "never" show indexes, "auto" = show indexes when a table has "index" column - show_empty: true # show 'empty list' and 'empty record' placeholders for command output - padding: { left: 1, right: 1 } # a left right padding of each column in a table - trim: { - methodology: wrapping # wrapping or truncating - wrapping_try_keep_words: true # A strategy used by the 'wrapping' methodology - truncating_suffix: "..." # A suffix used by the 'truncating' methodology - } - header_on_separator: false # show header text on separator/border line - footer_inheritance: false # render footer in parent table if child is big enough (extended table option) - # abbreviated_row_count: 10 # limit data rows from top and bottom after reaching a set point - } - - error_style: "fancy" # "fancy" or "plain" for screen reader-friendly error messages - - # Whether an error message should be printed if an error of a certain kind is triggered. - display_errors: { - exit_code: false # assume the external command prints an error message - # Core dump errors are always printed, and SIGPIPE never triggers an error. - # The setting below controls message printing for termination by all other signals. - termination_signal: true - } - - # datetime_format determines what a datetime rendered in the shell would look like. - # Behavior without this configuration point will be to "humanize" the datetime display, - # showing something like "a day ago." - datetime_format: { - # normal: '%a, %d %b %Y %H:%M:%S %z' # shows up in displays of variables or other datetime's outside of tables - # table: '%m/%d/%y %I:%M:%S%p' # generally shows up in tabular outputs such as ls. commenting this out will change it to the default human readable datetime format - } - - explore: { - status_bar_background: { fg: "#1D1F21", bg: "#C4C9C6" }, - command_bar_text: { fg: "#C4C9C6" }, - highlight: { fg: "black", bg: "yellow" }, - status: { - error: { fg: "white", bg: "red" }, - warn: {} - info: {} - }, - selected_cell: { bg: light_blue }, - } - - history: { - max_size: 100_000 # Session has to be reloaded for this to take effect - sync_on_enter: true # Enable to share history between multiple sessions, else you have to close the session to write history to file - file_format: "plaintext" # "sqlite" or "plaintext" - isolation: false # only available with sqlite file_format. true enables history isolation, false disables it. true will allow the history to be isolated to the current session using up/down arrows. false will allow the history to be shared across all sessions. - } - - completions: { - case_sensitive: false # set to true to enable case-sensitive completions - quick: true # set this to false to prevent auto-selecting completions when only one remains - partial: true # set this to false to prevent partial filling of the prompt - algorithm: "prefix" # prefix or fuzzy - sort: "smart" # "smart" (alphabetical for prefix matching, fuzzy score for fuzzy matching) or "alphabetical" - external: { - enable: true # set to false to prevent nushell looking into $env.PATH to find more suggestions, `false` recommended for WSL users as this look up may be very slow - max_results: 100 # setting it lower can improve completion performance at the cost of omitting some options - completer: null # check 'carapace_completer' above as an example - } - use_ls_colors: true # set this to true to enable file/path/directory completions using LS_COLORS - } - - filesize: { - metric: false # true => KB, MB, GB (ISO standard), false => KiB, MiB, GiB (Windows standard) - format: "auto" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, auto - } - - cursor_shape: { - emacs: line # block, underscore, line, blink_block, blink_underscore, blink_line, inherit to skip setting cursor shape (line is the default) - vi_insert: block # block, underscore, line, blink_block, blink_underscore, blink_line, inherit to skip setting cursor shape (block is the default) - vi_normal: underscore # block, underscore, line, blink_block, blink_underscore, blink_line, inherit to skip setting cursor shape (underscore is the default) - } - - color_config: $dark_theme # if you want a more interesting theme, you can replace the empty record with `$dark_theme`, `$light_theme` or another custom record - footer_mode: 25 # always, never, number_of_rows, auto - float_precision: 2 # the precision for displaying floats in tables - buffer_editor: null # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.VISUAL and $env.EDITOR - use_ansi_coloring: true - bracketed_paste: true # enable bracketed paste, currently useless on windows - edit_mode: emacs # emacs, vi - shell_integration: { - # osc2 abbreviates the path if in the home_dir, sets the tab/window title, shows the running command in the tab/window title - osc2: true - # osc7 is a way to communicate the path to the terminal, this is helpful for spawning new tabs in the same directory - osc7: true - # osc8 is also implemented as the deprecated setting ls.show_clickable_links, it shows clickable links in ls output if your terminal supports it. show_clickable_links is deprecated in favor of osc8 - osc8: true - # osc9_9 is from ConEmu and is starting to get wider support. It's similar to osc7 in that it communicates the path to the terminal - osc9_9: false - # osc133 is several escapes invented by Final Term which include the supported ones below. - # 133;A - Mark prompt start - # 133;B - Mark prompt end - # 133;C - Mark pre-execution - # 133;D;exit - Mark execution finished with exit code - # This is used to enable terminals to know where the prompt is, the command is, where the command finishes, and where the output of the command is - osc133: true - # osc633 is closely related to osc133 but only exists in visual studio code (vscode) and supports their shell integration features - # 633;A - Mark prompt start - # 633;B - Mark prompt end - # 633;C - Mark pre-execution - # 633;D;exit - Mark execution finished with exit code - # 633;E - Explicitly set the command line with an optional nonce - # 633;P;Cwd= - Mark the current working directory and communicate it to the terminal - # and also helps with the run recent menu in vscode - osc633: true - # reset_application_mode is escape \x1b[?1l and was added to help ssh work better - reset_application_mode: true - } - render_right_prompt_on_last_line: false # true or false to enable or disable right prompt to be rendered on last line of the prompt. - use_kitty_protocol: false # enables keyboard enhancement protocol implemented by kitty console, only if your terminal support this. - highlight_resolved_externals: false # true enables highlighting of external commands in the repl resolved by which. - recursion_limit: 50 # the maximum number of times nushell allows recursion before stopping it - - plugins: {} # Per-plugin configuration. See https://www.nushell.sh/contributor-book/plugins.html#configuration. - - plugin_gc: { - # Configuration for plugin garbage collection - default: { - enabled: true # true to enable stopping of inactive plugins - stop_after: 10sec # how long to wait after a plugin is inactive to stop it - } - plugins: { - # alternate configuration for specific plugins, by name, for example: - # - # gstat: { - # enabled: false - # } - } - } - - hooks: { - pre_prompt: [{ null }] # run before the prompt is shown - pre_execution: [{ null }] # run before the repl input is run - env_change: { - PWD: [{|before, after| null }] # run if the PWD environment is different since the last repl input - } - display_output: "if (term size).columns >= 100 { table -e } else { table }" # run to display the output of a pipeline - command_not_found: { null } # return an error message when a command is not found - } - - menus: [ - # Configuration for default nushell menus - # Note the lack of source parameter - { - name: completion_menu - only_buffer_difference: false - marker: "| " - type: { - layout: columnar - columns: 4 - col_width: 20 # Optional value. If missing all the screen width is used to calculate column width - col_padding: 2 - } - style: { - text: green - selected_text: { attr: r } - description_text: yellow - match_text: { attr: u } - selected_match_text: { attr: ur } - } - } - { - name: ide_completion_menu - only_buffer_difference: false - marker: "| " - type: { - layout: ide - min_completion_width: 0, - max_completion_width: 50, - max_completion_height: 10, # will be limited by the available lines in the terminal - padding: 0, - border: true, - cursor_offset: 0, - description_mode: "prefer_right" - min_description_width: 0 - max_description_width: 50 - max_description_height: 10 - description_offset: 1 - # If true, the cursor pos will be corrected, so the suggestions match up with the typed text - # - # C:\> str - # str join - # str trim - # str split - correct_cursor_pos: false - } - style: { - text: green - selected_text: { attr: r } - description_text: yellow - match_text: { attr: u } - selected_match_text: { attr: ur } - } - } - { - name: history_menu - only_buffer_difference: true - marker: "? " - type: { - layout: list - page_size: 10 - } - style: { - text: green - selected_text: green_reverse - description_text: yellow - } - } - { - name: help_menu - only_buffer_difference: true - marker: "? " - type: { - layout: description - columns: 4 - col_width: 20 # Optional value. If missing all the screen width is used to calculate column width - col_padding: 2 - selection_rows: 4 - description_rows: 10 - } - style: { - text: green - selected_text: green_reverse - description_text: yellow - } - } - ] - - keybindings: [ - { - name: completion_menu - modifier: none - keycode: tab - mode: [emacs vi_normal vi_insert] - event: { - until: [ - { send: menu name: completion_menu } - { send: menunext } - { edit: complete } - ] - } - } - { - name: completion_previous_menu - modifier: shift - keycode: backtab - mode: [emacs, vi_normal, vi_insert] - event: { send: menuprevious } - } - { - name: ide_completion_menu - modifier: control - keycode: space - mode: [emacs vi_normal vi_insert] - event: { - until: [ - { send: menu name: ide_completion_menu } - { send: menunext } - { edit: complete } - ] - } - } - { - name: history_menu - modifier: control - keycode: char_r - mode: [emacs, vi_insert, vi_normal] - event: { send: menu name: history_menu } - } - { - name: help_menu - modifier: none - keycode: f1 - mode: [emacs, vi_insert, vi_normal] - event: { send: menu name: help_menu } - } - { - name: next_page_menu - modifier: control - keycode: char_x - mode: emacs - event: { send: menupagenext } - } - { - name: undo_or_previous_page_menu - modifier: control - keycode: char_z - mode: emacs - event: { - until: [ - { send: menupageprevious } - { edit: undo } - ] - } - } - { - name: escape - modifier: none - keycode: escape - mode: [emacs, vi_normal, vi_insert] - event: { send: esc } # NOTE: does not appear to work - } - { - name: cancel_command - modifier: control - keycode: char_c - mode: [emacs, vi_normal, vi_insert] - event: { send: ctrlc } - } - { - name: quit_shell - modifier: control - keycode: char_d - mode: [emacs, vi_normal, vi_insert] - event: { send: ctrld } - } - { - name: clear_screen - modifier: control - keycode: char_l - mode: [emacs, vi_normal, vi_insert] - event: { send: clearscreen } - } - { - name: search_history - modifier: control - keycode: char_q - mode: [emacs, vi_normal, vi_insert] - event: { send: searchhistory } - } - { - name: open_command_editor - modifier: control - keycode: char_o - mode: [emacs, vi_normal, vi_insert] - event: { send: openeditor } - } - { - name: move_up - modifier: none - keycode: up - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: menuup } - { send: up } - ] - } - } - { - name: move_down - modifier: none - keycode: down - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: menudown } - { send: down } - ] - } - } - { - name: move_left - modifier: none - keycode: left - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: menuleft } - { send: left } - ] - } - } - { - name: move_right_or_take_history_hint - modifier: none - keycode: right - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: historyhintcomplete } - { send: menuright } - { send: right } - ] - } - } - { - name: move_one_word_left - modifier: control - keycode: left - mode: [emacs, vi_normal, vi_insert] - event: { edit: movewordleft } - } - { - name: move_one_word_right_or_take_history_hint - modifier: control - keycode: right - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: historyhintwordcomplete } - { edit: movewordright } - ] - } - } - { - name: move_to_line_start - modifier: none - keycode: home - mode: [emacs, vi_normal, vi_insert] - event: { edit: movetolinestart } - } - { - name: move_to_line_start - modifier: control - keycode: char_a - mode: [emacs, vi_normal, vi_insert] - event: { edit: movetolinestart } - } - { - name: move_to_line_end_or_take_history_hint - modifier: none - keycode: end - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: historyhintcomplete } - { edit: movetolineend } - ] - } - } - { - name: move_to_line_end_or_take_history_hint - modifier: control - keycode: char_e - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: historyhintcomplete } - { edit: movetolineend } - ] - } - } - { - name: move_to_line_start - modifier: control - keycode: home - mode: [emacs, vi_normal, vi_insert] - event: { edit: movetolinestart } - } - { - name: move_to_line_end - modifier: control - keycode: end - mode: [emacs, vi_normal, vi_insert] - event: { edit: movetolineend } - } - { - name: move_down - modifier: control - keycode: char_n - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: menudown } - { send: down } - ] - } - } - { - name: move_up - modifier: control - keycode: char_p - mode: [emacs, vi_normal, vi_insert] - event: { - until: [ - { send: menuup } - { send: up } - ] - } - } - { - name: delete_one_character_backward - modifier: none - keycode: backspace - mode: [emacs, vi_insert] - event: { edit: backspace } - } - { - name: delete_one_word_backward - modifier: control - keycode: backspace - mode: [emacs, vi_insert] - event: { edit: backspaceword } - } - { - name: delete_one_character_forward - modifier: none - keycode: delete - mode: [emacs, vi_insert] - event: { edit: delete } - } - { - name: delete_one_character_forward - modifier: control - keycode: delete - mode: [emacs, vi_insert] - event: { edit: delete } - } - { - name: delete_one_character_backward - modifier: control - keycode: char_h - mode: [emacs, vi_insert] - event: { edit: backspace } - } - { - name: delete_one_word_backward - modifier: control - keycode: char_w - mode: [emacs, vi_insert] - event: { edit: backspaceword } - } - { - name: move_left - modifier: none - keycode: backspace - mode: vi_normal - event: { edit: moveleft } - } - { - name: newline_or_run_command - modifier: none - keycode: enter - mode: emacs - event: { send: enter } - } - { - name: move_left - modifier: control - keycode: char_b - mode: emacs - event: { - until: [ - { send: menuleft } - { send: left } - ] - } - } - { - name: move_right_or_take_history_hint - modifier: control - keycode: char_f - mode: emacs - event: { - until: [ - { send: historyhintcomplete } - { send: menuright } - { send: right } - ] - } - } - { - name: redo_change - modifier: control - keycode: char_g - mode: emacs - event: { edit: redo } - } - { - name: undo_change - modifier: control - keycode: char_z - mode: emacs - event: { edit: undo } - } - { - name: paste_before - modifier: control - keycode: char_y - mode: emacs - event: { edit: pastecutbufferbefore } - } - { - name: cut_word_left - modifier: control - keycode: char_w - mode: emacs - event: { edit: cutwordleft } - } - { - name: cut_line_to_end - modifier: control - keycode: char_k - mode: emacs - event: { edit: cuttolineend } - } - { - name: cut_line_from_start - modifier: control - keycode: char_u - mode: emacs - event: { edit: cutfromstart } - } - { - name: swap_graphemes - modifier: control - keycode: char_t - mode: emacs - event: { edit: swapgraphemes } - } - { - name: move_one_word_left - modifier: alt - keycode: left - mode: emacs - event: { edit: movewordleft } - } - { - name: move_one_word_right_or_take_history_hint - modifier: alt - keycode: right - mode: emacs - event: { - until: [ - { send: historyhintwordcomplete } - { edit: movewordright } - ] - } - } - { - name: move_one_word_left - modifier: alt - keycode: char_b - mode: emacs - event: { edit: movewordleft } - } - { - name: move_one_word_right_or_take_history_hint - modifier: alt - keycode: char_f - mode: emacs - event: { - until: [ - { send: historyhintwordcomplete } - { edit: movewordright } - ] - } - } - { - name: delete_one_word_forward - modifier: alt - keycode: delete - mode: emacs - event: { edit: deleteword } - } - { - name: delete_one_word_backward - modifier: alt - keycode: backspace - mode: emacs - event: { edit: backspaceword } - } - { - name: delete_one_word_backward - modifier: alt - keycode: char_m - mode: emacs - event: { edit: backspaceword } - } - { - name: cut_word_to_right - modifier: alt - keycode: char_d - mode: emacs - event: { edit: cutwordright } - } - { - name: upper_case_word - modifier: alt - keycode: char_u - mode: emacs - event: { edit: uppercaseword } - } - { - name: lower_case_word - modifier: alt - keycode: char_l - mode: emacs - event: { edit: lowercaseword } - } - { - name: capitalize_char - modifier: alt - keycode: char_c - mode: emacs - event: { edit: capitalizechar } - } - # The following bindings with `*system` events require that Nushell has - # been compiled with the `system-clipboard` feature. - # If you want to use the system clipboard for visual selection or to - # paste directly, uncomment the respective lines and replace the version - # using the internal clipboard. - { - name: copy_selection - modifier: control_shift - keycode: char_c - mode: emacs - event: { edit: copyselection } - # event: { edit: copyselectionsystem } - } - { - name: cut_selection - modifier: control_shift - keycode: char_x - mode: emacs - event: { edit: cutselection } - # event: { edit: cutselectionsystem } - } - # { - # name: paste_system - # modifier: control_shift - # keycode: char_v - # mode: emacs - # event: { edit: pastesystem } - # } - { - name: select_all - modifier: control_shift - keycode: char_a - mode: emacs - event: { edit: selectall } - } - ] -} diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu deleted file mode 100644 index fb527dc112..0000000000 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ /dev/null @@ -1,101 +0,0 @@ -# Nushell Environment Config File -# -# version = "0.100.1" - -def create_left_prompt [] { - let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { - null => $env.PWD - '' => '~' - $relative_pwd => ([~ $relative_pwd] | path join) - } - - let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold }) - let separator_color = (if (is-admin) { ansi light_red_bold } else { ansi light_green_bold }) - let path_segment = $"($path_color)($dir)(ansi reset)" - - $path_segment | str replace --all (char path_sep) $"($separator_color)(char path_sep)($path_color)" -} - -def create_right_prompt [] { - # create a right prompt in magenta with green separators and am/pm underlined - let time_segment = ([ - (ansi reset) - (ansi magenta) - (date now | format date '%x %X') # try to respect user's locale - ] | str join | str replace --regex --all "([/:])" $"(ansi green)${1}(ansi magenta)" | - str replace --regex --all "([AP]M)" $"(ansi magenta_underline)${1}") - - let last_exit_code = if ($env.LAST_EXIT_CODE != 0) {([ - (ansi rb) - ($env.LAST_EXIT_CODE) - ] | str join) - } else { "" } - - ([$last_exit_code, (char space), $time_segment] | str join) -} - -# Use nushell functions to define your right and left prompt -$env.PROMPT_COMMAND = {|| create_left_prompt } -# FIXME: This default is not implemented in rust code as of 2023-09-08. -$env.PROMPT_COMMAND_RIGHT = {|| create_right_prompt } - -# The prompt indicators are environmental variables that represent -# the state of the prompt -$env.PROMPT_INDICATOR = {|| "> " } -$env.PROMPT_INDICATOR_VI_INSERT = {|| ": " } -$env.PROMPT_INDICATOR_VI_NORMAL = {|| "> " } -$env.PROMPT_MULTILINE_INDICATOR = {|| "::: " } - -# If you want previously entered commands to have a different prompt from the usual one, -# you can uncomment one or more of the following lines. -# This can be useful if you have a 2-line prompt and it's taking up a lot of space -# because every command entered takes up 2 lines instead of 1. You can then uncomment -# the line below so that previously entered commands show with a single `🚀`. -# $env.TRANSIENT_PROMPT_COMMAND = {|| "🚀 " } -# $env.TRANSIENT_PROMPT_INDICATOR = {|| "" } -# $env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = {|| "" } -# $env.TRANSIENT_PROMPT_INDICATOR_VI_NORMAL = {|| "" } -# $env.TRANSIENT_PROMPT_MULTILINE_INDICATOR = {|| "" } -# $env.TRANSIENT_PROMPT_COMMAND_RIGHT = {|| "" } - -# Specifies how environment variables are: -# - converted from a string to a value on Nushell startup (from_string) -# - converted from a value back to a string when running external commands (to_string) -# Note: The conversions happen *after* config.nu is loaded -$env.ENV_CONVERSIONS = { - "PATH": { - from_string: { |s| $s | split row (char esep) | path expand --no-symlink } - to_string: { |v| $v | path expand --no-symlink | str join (char esep) } - } - "Path": { - from_string: { |s| $s | split row (char esep) | path expand --no-symlink } - to_string: { |v| $v | path expand --no-symlink | str join (char esep) } - } -} - -# Directories to search for scripts when calling source or use -# The default for this is $nu.default-config-dir/scripts -$env.NU_LIB_DIRS = [ - ($nu.default-config-dir | path join 'scripts') # add /scripts - ($nu.data-dir | path join 'completions') # default home for nushell completions -] - -# Directories to search for plugin binaries when calling register -# The default for this is $nu.default-config-dir/plugins -$env.NU_PLUGIN_DIRS = [ - ($nu.default-config-dir | path join 'plugins') # add /plugins -] - -# To add entries to PATH (on Windows you might use Path), you can use the following pattern: -# $env.PATH = ($env.PATH | split row (char esep) | prepend '/some/path') -# An alternate way to add entries to $env.PATH is to use the custom command `path add` -# which is built into the nushell stdlib: -# use std "path add" -# $env.PATH = ($env.PATH | split row (char esep)) -# path add /some/path -# path add ($env.CARGO_HOME | path join "bin") -# path add ($env.HOME | path join ".local" "bin") -# $env.PATH = ($env.PATH | uniq) - -# To load from a custom file you can use: -# source ($nu.default-config-dir | path join 'custom.nu') diff --git a/crates/nu-utils/src/utils.rs b/crates/nu-utils/src/utils.rs index 472afc92f7..798e542be5 100644 --- a/crates/nu-utils/src/utils.rs +++ b/crates/nu-utils/src/utils.rs @@ -85,12 +85,29 @@ where ret } +// See default_files/README.md for a description of these files pub fn get_default_env() -> &'static str { - include_str!("sample_config/default_env.nu") + include_str!("default_files/default_env.nu") +} + +pub fn get_scaffold_env() -> &'static str { + include_str!("default_files/scaffold_env.nu") +} + +pub fn get_sample_env() -> &'static str { + include_str!("default_files/sample_env.nu") } pub fn get_default_config() -> &'static str { - include_str!("sample_config/default_config.nu") + include_str!("default_files/default_config.nu") +} + +pub fn get_scaffold_config() -> &'static str { + include_str!("default_files/scaffold_config.nu") +} + +pub fn get_sample_config() -> &'static str { + include_str!("default_files/sample_config.nu") } pub fn get_ls_colors(lscolors_env_string: Option) -> LsColors { diff --git a/src/config_files.rs b/src/config_files.rs index 7631a951bc..5c43764580 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -2,12 +2,13 @@ use log::warn; #[cfg(feature = "plugin")] use nu_cli::read_plugin_file; use nu_cli::{eval_config_contents, eval_source}; +use nu_engine::convert_env_values; use nu_path::canonicalize_with; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, report_parse_error, report_shell_error, Config, ParseError, PipelineData, Spanned, }; -use nu_utils::{get_default_config, get_default_env}; +use nu_utils::{get_default_config, get_default_env, get_scaffold_config, get_scaffold_env, perf}; use std::{ fs, fs::File, @@ -26,12 +27,47 @@ pub(crate) fn read_config_file( stack: &mut Stack, config_file: Option>, is_env_config: bool, - ask_to_create: bool, + create_scaffold: bool, ) { warn!( "read_config_file() config_file_specified: {:?}, is_env_config: {is_env_config}", &config_file ); + + if is_env_config { + eval_default_config(engine_state, stack, get_default_env(), is_env_config); + + let start_time = std::time::Instant::now(); + let config = engine_state.get_config(); + let use_color = config.use_ansi_coloring; + // Translate environment variables from Strings to Values + if let Err(e) = convert_env_values(engine_state, stack) { + report_shell_error(engine_state, &e); + } + + perf!( + "translate env vars after default_env.nu", + start_time, + use_color + ); + } else { + let start_time = std::time::Instant::now(); + let config = engine_state.get_config(); + let use_color = config.use_ansi_coloring; + if let Err(e) = convert_env_values(engine_state, stack) { + report_shell_error(engine_state, &e); + } + perf!( + "translate env vars before default_config.nu", + start_time, + use_color + ); + + eval_default_config(engine_state, stack, get_default_config(), is_env_config); + }; + + warn!("read_config_file() loading_defaults is_env_config: {is_env_config}"); + // Load config startup file if let Some(file) = config_file { match engine_state.cwd_as_string(Some(stack)) { @@ -59,41 +95,16 @@ pub(crate) fn read_config_file( config_path.push(if is_env_config { ENV_FILE } else { CONFIG_FILE }); if !config_path.exists() { - let file_msg = if is_env_config { - "environment config" + let scaffold_config_file = if is_env_config { + get_scaffold_env() } else { - "config" + get_scaffold_config() }; - let will_create_file = match ask_to_create { - true => { - println!( - "No {} file found at {}", - file_msg, - config_path.to_string_lossy() - ); - println!("Would you like to create one with defaults (Y/n): "); - - let mut answer = String::new(); - std::io::stdin() - .read_line(&mut answer) - .expect("Failed to read user input"); - - matches!(answer.trim(), "y" | "Y" | "") - } - _ => false, - }; - - let config_file = if is_env_config { - get_default_env() - } else { - get_default_config() - }; - - match will_create_file { + match create_scaffold { true => { if let Ok(mut output) = File::create(&config_path) { - if write!(output, "{config_file}").is_ok() { + if write!(output, "{scaffold_config_file}").is_ok() { let config_type = if is_env_config { "Environment config" } else { @@ -109,17 +120,14 @@ pub(crate) fn read_config_file( "Unable to write to {}, sourcing default file instead", config_path.to_string_lossy(), ); - eval_default_config(engine_state, stack, config_file, is_env_config); return; } } else { - eprintln!("Unable to create {config_file}, sourcing default file instead"); - eval_default_config(engine_state, stack, config_file, is_env_config); + eprintln!("Unable to create {scaffold_config_file}"); return; } } _ => { - eval_default_config(engine_state, stack, config_file, is_env_config); return; } } @@ -227,11 +235,7 @@ fn eval_default_config( config_file: &str, is_env_config: bool, ) { - warn!( - "eval_default_config() config_file_specified: {:?}, is_env_config: {}", - &config_file, is_env_config - ); - // Just use the contents of "default_config.nu" or "default_env.nu" + warn!("eval_default_config() is_env_config: {}", is_env_config); eval_source( engine_state, stack, @@ -264,20 +268,14 @@ pub(crate) fn setup_config( &config_file, &env_file, is_login_shell ); - let ask_to_create_config = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); + let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); let result = catch_unwind(AssertUnwindSafe(|| { #[cfg(feature = "plugin")] read_plugin_file(engine_state, plugin_file); - read_config_file(engine_state, stack, env_file, true, ask_to_create_config); - read_config_file( - engine_state, - stack, - config_file, - false, - ask_to_create_config, - ); + read_config_file(engine_state, stack, env_file, true, create_scaffold); + read_config_file(engine_state, stack, config_file, false, create_scaffold); if is_login_shell { read_loginshell_file(engine_state, stack); diff --git a/src/run.rs b/src/run.rs index 1cbfbc03a2..89bad3866f 100644 --- a/src/run.rs +++ b/src/run.rs @@ -23,7 +23,7 @@ pub(crate) fn run_commands( trace!("run_commands"); let start_time = std::time::Instant::now(); - let ask_to_create_config = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); + let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); let mut stack = Stack::new(); @@ -46,7 +46,7 @@ pub(crate) fn run_commands( &mut stack, parsed_nu_cli_args.env_file, true, - ask_to_create_config, + create_scaffold, ); } else { config_files::read_default_env_file(engine_state, &mut stack) @@ -55,7 +55,7 @@ pub(crate) fn run_commands( perf!("read env.nu", start_time, use_color); let start_time = std::time::Instant::now(); - let ask_to_create_config = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); + let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); // If we have a config file parameter *OR* we have a login shell parameter, read the config file if parsed_nu_cli_args.config_file.is_some() || parsed_nu_cli_args.login_shell.is_some() { @@ -64,7 +64,7 @@ pub(crate) fn run_commands( &mut stack, parsed_nu_cli_args.config_file, false, - ask_to_create_config, + create_scaffold, ); } @@ -123,7 +123,7 @@ pub(crate) fn run_file( // if the --no-config-file(-n) flag is passed, do not load plugin, env, or config files if parsed_nu_cli_args.no_config_file.is_none() { let start_time = std::time::Instant::now(); - let ask_to_create_config = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); + let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists()); #[cfg(feature = "plugin")] read_plugin_file(engine_state, parsed_nu_cli_args.plugin_file); perf!("read plugins", start_time, use_color); @@ -136,7 +136,7 @@ pub(crate) fn run_file( &mut stack, parsed_nu_cli_args.env_file, true, - ask_to_create_config, + create_scaffold, ); } else { config_files::read_default_env_file(engine_state, &mut stack) @@ -150,7 +150,7 @@ pub(crate) fn run_file( &mut stack, parsed_nu_cli_args.config_file, false, - ask_to_create_config, + create_scaffold, ); } perf!("read config.nu", start_time, use_color); diff --git a/tests/repl/test_config_path.rs b/tests/repl/test_config_path.rs index c812f3404c..63488ecb6a 100644 --- a/tests/repl/test_config_path.rs +++ b/tests/repl/test_config_path.rs @@ -221,8 +221,8 @@ fn test_default_config_path_symlinked_config_files() { #[test] fn test_alternate_config_path() { - let config_file = "crates/nu-utils/src/sample_config/default_config.nu"; - let env_file = "crates/nu-utils/src/sample_config/default_env.nu"; + let config_file = "crates/nu-utils/src/default_files/scaffold_config.nu"; + let env_file = "crates/nu-utils/src/default_files/scaffold_env.nu"; let cwd = std::env::current_dir().expect("Could not get current working directory"); From d8c24936588659fed0f340543e2a7c6f5d35782e Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Thu, 21 Nov 2024 01:47:03 -0800 Subject: [PATCH 17/43] Deprecate `split-by` command (#14019) # Description I'm not quite sure what the point of the `split-by` command is. The only example for the command seems to suggest it's an additional grouping command. I.e., a record that seems to be the output of the `group-by` command is passed to `split-by` which then adds an additional layer of grouping based on a different column. # User-Facing Changes Breaking change, deprecated the command. --- crates/nu-command/src/filters/split_by.rs | 10 ++++++++++ crates/nu-protocol/src/errors/shell_error.rs | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/nu-command/src/filters/split_by.rs b/crates/nu-command/src/filters/split_by.rs index 217918cf91..b42f0c820c 100644 --- a/crates/nu-command/src/filters/split_by.rs +++ b/crates/nu-command/src/filters/split_by.rs @@ -1,5 +1,6 @@ use indexmap::IndexMap; use nu_engine::command_prelude::*; +use nu_protocol::report_shell_warning; #[derive(Clone)] pub struct SplitBy; @@ -27,6 +28,15 @@ impl Command for SplitBy { call: &Call, input: PipelineData, ) -> Result { + report_shell_warning( + engine_state, + &ShellError::Deprecated { + deprecated: "The `split_by` command", + suggestion: "Please use the `group-by` command instead.", + span: call.head, + help: None, + }, + ); split_by(engine_state, stack, call, input) } diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index 96678daef5..b4b6c29cee 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1330,14 +1330,15 @@ This is an internal Nushell error, please file an issue https://github.com/nushe span: Span, }, - #[error("Deprecated: {old_command}")] - #[diagnostic(help("for more info see {url}"))] + #[error("{deprecated} is deprecated and will be removed in a future release")] + #[diagnostic()] Deprecated { - old_command: String, - new_suggestion: String, - #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead.")] + deprecated: &'static str, + suggestion: &'static str, + #[label("{deprecated} is deprecated. {suggestion}")] span: Span, - url: String, + #[help] + help: Option<&'static str>, }, /// Invalid glob pattern From e63976df7e021693f34bcb299575beeeced6e66f Mon Sep 17 00:00:00 2001 From: Marc Schreiber Date: Thu, 21 Nov 2024 13:31:14 +0100 Subject: [PATCH 18/43] Bump Calamine (#14403) This commit upgrades calamine in order to benefit from recent developments, e.g. ignore annotations in column headers (see https://github.com/tafia/calamine/pull/467 for reference). --- Cargo.lock | 62 ++++++++++++++++-- Cargo.toml | 2 +- .../tests/format_conversions/ods.rs | 17 +++++ .../formats/sample_data_with_annotation.ods | Bin 0 -> 71562 bytes 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/formats/sample_data_with_annotation.ods diff --git a/Cargo.lock b/Cargo.lock index 7f43aaada7..d15ecdf729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,15 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.4.1" @@ -578,16 +587,15 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "calamine" -version = "0.24.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3a315226fdc5b1c3e33521073e1712a05944bc0664d665ff1f6ff0396334da" +checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1" dependencies = [ "byteorder", "chrono", "codepage", "encoding_rs", "log", - "once_cell", "quick-xml 0.31.0", "serde", "zip", @@ -1183,6 +1191,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -2676,6 +2695,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -5945,6 +5970,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simd-json" version = "0.13.11" @@ -7787,14 +7818,33 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.6" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ - "byteorder", + "arbitrary", "crc32fast", "crossbeam-utils", + "displaydoc", "flate2", + "indexmap", + "memchr", + "thiserror 1.0.69", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b9be5c59f3..cf836ea6e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ brotli = "6.0" byteorder = "1.5" bytes = "1" bytesize = "1.3" -calamine = "0.24.0" +calamine = "0.26.1" chardetng = "0.1.17" chrono = { default-features = false, version = "0.4.34" } chrono-humanize = "0.2.3" diff --git a/crates/nu-command/tests/format_conversions/ods.rs b/crates/nu-command/tests/format_conversions/ods.rs index 34e3e5208c..8a69493cc4 100644 --- a/crates/nu-command/tests/format_conversions/ods.rs +++ b/crates/nu-command/tests/format_conversions/ods.rs @@ -29,3 +29,20 @@ fn from_ods_file_to_table_select_sheet() { assert_eq!(actual.out, "SalesOrders"); } + +#[test] +fn from_ods_file_to_table_select_sheet_with_annotations() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_data_with_annotation.ods --raw + | from ods --sheets ["SalesOrders"] + | get SalesOrders + | get column4 + | get 0 + "# + )); + + // The Units column in the sheet SalesOrders has an annotation and should be ignored. + assert_eq!(actual.out, "Units"); +} diff --git a/tests/fixtures/formats/sample_data_with_annotation.ods b/tests/fixtures/formats/sample_data_with_annotation.ods new file mode 100644 index 0000000000000000000000000000000000000000..f4b5c723ea2bc64453917d3463bcd7d185c60460 GIT binary patch literal 71562 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 literal 0 HcmV?d00001 From 2a90cb73553f74db28dbb2cd881e464214ea4936 Mon Sep 17 00:00:00 2001 From: Rikuki IX <52599939+rikukiix@users.noreply.github.com> Date: Fri, 22 Nov 2024 05:57:33 +0800 Subject: [PATCH 19/43] Update SHLVL (only when interactive) on startup (#14404) # Description Make NuShell correctly inherit and update `SHLVL` from other shells (obviously including itself) in Unix environment. See issue #14384 # User-Facing Changes None # Tests + Formatting New code formatted. New feature works well in interactive usage. # After Submitting --- src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.rs b/src/main.rs index b7c5a6a173..2c7b94b628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -280,6 +280,16 @@ fn main() -> Result<()> { Value::string(env!("CARGO_PKG_VERSION"), Span::unknown()), ); + // Add SHLVL if interactive + if engine_state.is_interactive { + let mut shlvl = engine_state + .get_env_var("SHLVL") + .map(|x| x.as_str().unwrap_or("0").parse::().unwrap_or(0)) + .unwrap_or(0); + shlvl += 1; + engine_state.add_env_var("SHLVL".to_string(), Value::int(shlvl, Span::unknown())); + } + if parsed_nu_cli_args.no_std_lib.is_none() { load_standard_library(&mut engine_state)?; } From 5f7082f0534b70884e33d3cab19dc82687487efe Mon Sep 17 00:00:00 2001 From: Bahex Date: Fri, 22 Nov 2024 00:58:31 +0300 Subject: [PATCH 20/43] truly flexible csv/tsv parsing (#14399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixes #14398 I will properly fill out this PR and fix any tests that might break when I have the time, this was a quick fix. # Description This PR makes `from csv` and `from tsv`, with the `--flexible` flag, stop dropping extra/unexpected columns. # User-Facing Changes `$text`'s contents ```csv value 1,aaa 2,bbb 3 4,ddd 5,eee,extra ``` Old behavior ```nushell > $text | from csv --flexible --noheaders ╭─#─┬─column0─╮ │ 0 │ value │ │ 1 │ 1 │ │ 2 │ 2 │ │ 3 │ 3 │ │ 4 │ 4 │ │ 5 │ 5 │ ╰─#─┴─column0─╯ ``` New behavior ```nushell > $text | from csv --flexible --noheaders ╭─#─┬─column0─┬─column1─┬─column2─╮ │ 0 │ value │ ❎ │ ❎ │ │ 1 │ 1 │ aaa │ ❎ │ │ 2 │ 2 │ bbb │ ❎ │ │ 3 │ 3 │ ❎ │ ❎ │ │ 4 │ 4 │ ddd │ ❎ │ │ 5 │ 5 │ eee │ extra │ ╰─#─┴─column0─┴─column1─┴─column2─╯ ``` - The first line in a csv (or tsv) document no longer limits the number of columns - Missing values in columns are longer automatically filled with `null` with this change, as a later row can introduce new columns. **BREAKING CHANGE** Because missing columns are different from empty columns, operations on possibly missing columns will have to use optional access syntax e.g. `get foo` => `get foo?` # Tests + Formatting Added examples that run as tests and adjusted existing tests to confirm the new behavior. # After Submitting Update the workaround with fish completer mentioned [here](https://www.nushell.sh/cookbook/external_completers.html#fish-completer) --- crates/nu-command/src/formats/from/csv.rs | 25 ++++++++++- .../nu-command/src/formats/from/delimited.rs | 43 ++++++++----------- crates/nu-command/src/formats/from/tsv.rs | 20 ++++++++- .../tests/format_conversions/csv.rs | 4 +- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/crates/nu-command/src/formats/from/csv.rs b/crates/nu-command/src/formats/from/csv.rs index 472ccf4d71..bde84c2c73 100644 --- a/crates/nu-command/src/formats/from/csv.rs +++ b/crates/nu-command/src/formats/from/csv.rs @@ -11,7 +11,10 @@ impl Command for FromCsv { fn signature(&self) -> Signature { Signature::build("from csv") - .input_output_types(vec![(Type::String, Type::table())]) + .input_output_types(vec![ + (Type::String, Type::table()), + (Type::String, Type::list(Type::Any)), + ]) .named( "separator", SyntaxShape::String, @@ -82,6 +85,26 @@ impl Command for FromCsv { })], )) }, + Example { + description: "Convert comma-separated data to a table, allowing variable number of columns per row", + example: "\"ColA,ColB\n1,2\n3,4,5\n6\" | from csv --flexible", + result: Some(Value::test_list ( + vec![ + Value::test_record(record! { + "ColA" => Value::test_int(1), + "ColB" => Value::test_int(2), + }), + Value::test_record(record! { + "ColA" => Value::test_int(3), + "ColB" => Value::test_int(4), + "column2" => Value::test_int(5), + }), + Value::test_record(record! { + "ColA" => Value::test_int(6), + }), + ], + )) + }, Example { description: "Convert comma-separated data to a table, ignoring headers", example: "open data.txt | from csv --noheaders", diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 0fea7e082b..5dfdd4ad82 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -39,12 +39,7 @@ fn from_delimited_stream( .from_reader(input_reader); let headers = if noheaders { - (0..reader - .headers() - .map_err(|err| from_csv_error(err, span))? - .len()) - .map(|i| format!("column{i}")) - .collect::>() + vec![] } else { reader .headers() @@ -54,32 +49,28 @@ fn from_delimited_stream( .collect() }; + let n = headers.len(); + let columns = headers + .into_iter() + .chain((n..).map(|i| format!("column{i}"))); let iter = reader.into_records().map(move |row| { let row = match row { Ok(row) => row, Err(err) => return Value::error(from_csv_error(err, span), span), }; - let columns = headers.iter().cloned(); - let values = row - .into_iter() - .map(|s| { - if no_infer { - Value::string(s, span) - } else if let Ok(i) = s.parse() { - Value::int(i, span) - } else if let Ok(f) = s.parse() { - Value::float(f, span) - } else { - Value::string(s, span) - } - }) - .chain(std::iter::repeat(Value::nothing(span))); + let columns = columns.clone(); + let values = row.into_iter().map(|s| { + if no_infer { + Value::string(s, span) + } else if let Ok(i) = s.parse() { + Value::int(i, span) + } else if let Ok(f) = s.parse() { + Value::float(f, span) + } else { + Value::string(s, span) + } + }); - // If there are more values than the number of headers, - // then the remaining values are ignored. - // - // Otherwise, if there are less values than headers, - // then `Value::nothing(span)` is used to fill the remaining columns. Value::record(columns.zip(values).collect(), span) }); diff --git a/crates/nu-command/src/formats/from/tsv.rs b/crates/nu-command/src/formats/from/tsv.rs index cd3c9f97bd..09bee4803f 100644 --- a/crates/nu-command/src/formats/from/tsv.rs +++ b/crates/nu-command/src/formats/from/tsv.rs @@ -11,7 +11,10 @@ impl Command for FromTsv { fn signature(&self) -> Signature { Signature::build("from tsv") - .input_output_types(vec![(Type::String, Type::table())]) + .input_output_types(vec![ + (Type::String, Type::table()), + (Type::String, Type::list(Type::Any)), + ]) .named( "comment", SyntaxShape::String, @@ -76,6 +79,21 @@ impl Command for FromTsv { })], )) }, + Example { + description: "Convert comma-separated data to a table, allowing variable number of columns per row and ignoring headers", + example: "\"value 1\nvalue 2\tdescription 2\" | from tsv --flexible --noheaders", + result: Some(Value::test_list ( + vec![ + Value::test_record(record! { + "column0" => Value::test_string("value 1"), + }), + Value::test_record(record! { + "column0" => Value::test_string("value 2"), + "column1" => Value::test_string("description 2"), + }), + ], + )) + }, Example { description: "Create a tsv file with header columns and open it", example: r#"$'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv"#, diff --git a/crates/nu-command/tests/format_conversions/csv.rs b/crates/nu-command/tests/format_conversions/csv.rs index f10a84b672..4f50ab1492 100644 --- a/crates/nu-command/tests/format_conversions/csv.rs +++ b/crates/nu-command/tests/format_conversions/csv.rs @@ -469,7 +469,7 @@ fn from_csv_test_flexible_extra_vals() { echo "a,b\n1,2,3" | from csv --flexible | first | values | to nuon "# )); - assert_eq!(actual.out, "[1, 2]"); + assert_eq!(actual.out, "[1, 2, 3]"); } #[test] @@ -479,5 +479,5 @@ fn from_csv_test_flexible_missing_vals() { echo "a,b\n1" | from csv --flexible | first | values | to nuon "# )); - assert_eq!(actual.out, "[1, null]"); + assert_eq!(actual.out, "[1]"); } From 671640b0a9bc58b10daea47f4dc49f725c47e523 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 22 Nov 2024 07:29:00 -0500 Subject: [PATCH 21/43] Avoid recomputing fuzzy match scores (#13700) # Description This PR makes it so that when using fuzzy matching, the score isn't recomputed when sorting. Instead, filtering and sorting suggestions is handled by a new `NuMatcher` struct. This struct accepts suggestions and, if they match the user's typed text, stores those suggestions (along with their scores and values). At the end, it returns a sorted list of suggestions. This probably won't have a noticeable impact on performance, but it might be helpful if we start using Nucleo in the future. Minor change: Makes `find_commands_by_predicate` in `StateWorkingSet` and `EngineState` take `FnMut` rather than `Fn` for the predicate. # User-Facing Changes When using case-insensitive matching, if you have two matches `FOO` and `abc`, `abc` will be shown before `FOO` rather than the other way around. I think this way makes more sense than the current behavior. When I brought this up on Discord, WindSoilder did say it would make sense to show uppercase matches first if the user typed, say, `F`. However, that would be a lot more complicated to implement. # Tests + Formatting Added a test for the changes in https://github.com/nushell/nushell/pull/13302. # After Submitting --- .../src/completions/command_completions.rs | 176 +++++++------- .../src/completions/completion_common.rs | 217 ++++++++--------- .../src/completions/completion_options.rs | 228 ++++++++++++++---- .../src/completions/custom_completions.rs | 45 +--- .../src/completions/directory_completions.rs | 15 +- .../src/completions/dotnu_completions.rs | 83 +++---- .../src/completions/file_completions.rs | 31 +-- .../src/completions/flag_completions.rs | 58 +++-- crates/nu-cli/src/completions/mod.rs | 2 +- .../src/completions/operator_completions.rs | 24 +- .../src/completions/variable_completions.rs | 156 ++++-------- crates/nu-cli/tests/completions/mod.rs | 23 +- crates/nu-protocol/src/engine/engine_state.rs | 2 +- .../src/engine/state_working_set.rs | 2 +- 14 files changed, 551 insertions(+), 511 deletions(-) diff --git a/crates/nu-cli/src/completions/command_completions.rs b/crates/nu-cli/src/completions/command_completions.rs index 0086270ac5..b12df4735a 100644 --- a/crates/nu-cli/src/completions/command_completions.rs +++ b/crates/nu-cli/src/completions/command_completions.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; + use crate::{ - completions::{Completer, CompletionOptions, MatchAlgorithm}, + completions::{Completer, CompletionOptions}, SuggestionKind, }; use nu_parser::FlatShape; @@ -9,7 +11,7 @@ use nu_protocol::{ }; use reedline::Suggestion; -use super::{completion_common::sort_suggestions, SemanticSuggestion}; +use super::{completion_options::NuMatcher, SemanticSuggestion}; pub struct CommandCompletion { flattened: Vec<(Span, FlatShape)>, @@ -33,10 +35,11 @@ impl CommandCompletion { fn external_command_completion( &self, working_set: &StateWorkingSet, - prefix: &str, - match_algorithm: MatchAlgorithm, - ) -> Vec { - let mut executables = vec![]; + sugg_span: reedline::Span, + matched_internal: impl Fn(&str) -> bool, + matcher: &mut NuMatcher, + ) -> HashMap { + let mut suggs = HashMap::new(); // os agnostic way to get the PATH env var let paths = working_set.permanent_state.get_path_env_var(); @@ -54,24 +57,38 @@ impl CommandCompletion { .completions .external .max_results - > executables.len() as i64 - && !executables.contains( - &item - .path() - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(), - ) - && matches!( - item.path().file_name().map(|x| match_algorithm - .matches_str(&x.to_string_lossy(), prefix)), - Some(true) - ) - && is_executable::is_executable(item.path()) + <= suggs.len() as i64 { - if let Ok(name) = item.file_name().into_string() { - executables.push(name); - } + break; + } + let Ok(name) = item.file_name().into_string() else { + continue; + }; + let value = if matched_internal(&name) { + format!("^{}", name) + } else { + name.clone() + }; + if suggs.contains_key(&value) { + continue; + } + if matcher.matches(&name) && is_executable::is_executable(item.path()) { + // If there's an internal command with the same name, adds ^cmd to the + // matcher so that both the internal and external command are included + matcher.add(&name, value.clone()); + suggs.insert( + value.clone(), + SemanticSuggestion { + suggestion: Suggestion { + value, + span: sugg_span, + append_whitespace: true, + ..Default::default() + }, + // TODO: is there a way to create a test? + kind: None, + }, + ); } } } @@ -79,7 +96,7 @@ impl CommandCompletion { } } - executables + suggs } fn complete_commands( @@ -88,68 +105,59 @@ impl CommandCompletion { span: Span, offset: usize, find_externals: bool, - match_algorithm: MatchAlgorithm, + options: &CompletionOptions, ) -> Vec { let partial = working_set.get_span_contents(span); + let mut matcher = NuMatcher::new(String::from_utf8_lossy(partial), options.clone()); - let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial); + let sugg_span = reedline::Span::new(span.start - offset, span.end - offset); - let mut results = working_set - .find_commands_by_predicate(filter_predicate, true) - .into_iter() - .map(move |x| SemanticSuggestion { - suggestion: Suggestion { - value: String::from_utf8_lossy(&x.0).to_string(), - description: x.1, - span: reedline::Span::new(span.start - offset, span.end - offset), - append_whitespace: true, - ..Suggestion::default() - }, - kind: Some(SuggestionKind::Command(x.2)), - }) - .collect::>(); - - let partial = working_set.get_span_contents(span); - let partial = String::from_utf8_lossy(partial).to_string(); - - if find_externals { - let results_external = self - .external_command_completion(working_set, &partial, match_algorithm) - .into_iter() - .map(move |x| SemanticSuggestion { + let mut internal_suggs = HashMap::new(); + let filtered_commands = working_set.find_commands_by_predicate( + |name| { + let name = String::from_utf8_lossy(name); + matcher.add(&name, name.to_string()) + }, + true, + ); + for (name, description, typ) in filtered_commands { + let name = String::from_utf8_lossy(&name); + internal_suggs.insert( + name.to_string(), + SemanticSuggestion { suggestion: Suggestion { - value: x, - span: reedline::Span::new(span.start - offset, span.end - offset), + value: name.to_string(), + description, + span: sugg_span, append_whitespace: true, ..Suggestion::default() }, - // TODO: is there a way to create a test? - kind: None, - }); - - let results_strings: Vec = - results.iter().map(|x| x.suggestion.value.clone()).collect(); - - for external in results_external { - if results_strings.contains(&external.suggestion.value) { - results.push(SemanticSuggestion { - suggestion: Suggestion { - value: format!("^{}", external.suggestion.value), - span: external.suggestion.span, - append_whitespace: true, - ..Suggestion::default() - }, - kind: external.kind, - }) - } else { - results.push(external) - } - } - - results - } else { - results + kind: Some(SuggestionKind::Command(typ)), + }, + ); } + + let mut external_suggs = if find_externals { + self.external_command_completion( + working_set, + sugg_span, + |name| internal_suggs.contains_key(name), + &mut matcher, + ) + } else { + HashMap::new() + }; + + let mut res = Vec::new(); + for cmd_name in matcher.results() { + if let Some(sugg) = internal_suggs + .remove(&cmd_name) + .or_else(|| external_suggs.remove(&cmd_name)) + { + res.push(sugg); + } + } + res } } @@ -158,7 +166,7 @@ impl Completer for CommandCompletion { &mut self, working_set: &StateWorkingSet, _stack: &Stack, - prefix: &[u8], + _prefix: &[u8], span: Span, offset: usize, pos: usize, @@ -188,18 +196,18 @@ impl Completer for CommandCompletion { Span::new(last.0.start, pos), offset, false, - options.match_algorithm, + options, ) } else { vec![] }; if !subcommands.is_empty() { - return sort_suggestions(&String::from_utf8_lossy(prefix), subcommands, options); + return subcommands; } let config = working_set.get_config(); - let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External) + if matches!(self.flat_shape, nu_parser::FlatShape::External) || matches!(self.flat_shape, nu_parser::FlatShape::InternalCall(_)) || ((span.end - span.start) == 0) || is_passthrough_command(working_set.delta.get_file_contents()) @@ -214,13 +222,11 @@ impl Completer for CommandCompletion { span, offset, config.completions.external.enable, - options.match_algorithm, + options, ) } else { vec![] - }; - - sort_suggestions(&String::from_utf8_lossy(prefix), commands, options) + } } } diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index fc7a5fcd2c..4b5ef857e1 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -1,22 +1,20 @@ -use super::MatchAlgorithm; -use crate::{ - completions::{matches, CompletionOptions}, - SemanticSuggestion, -}; -use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; +use super::{completion_options::NuMatcher, MatchAlgorithm}; +use crate::completions::CompletionOptions; use nu_ansi_term::Style; use nu_engine::env_to_string; use nu_path::dots::expand_ndots; use nu_path::{expand_to_real_path, home_dir}; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - CompletionSort, Span, + Span, }; use nu_utils::get_ls_colors; +use nu_utils::IgnoreCaseExt; use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP}; #[derive(Clone, Default)] pub struct PathBuiltFromString { + cwd: PathBuf, parts: Vec, isdir: bool, } @@ -30,76 +28,84 @@ pub struct PathBuiltFromString { /// want_directory: Whether we want only directories as completion matches. /// Some commands like `cd` can only be run on directories whereas others /// like `ls` can be run on regular files as well. -pub fn complete_rec( +fn complete_rec( partial: &[&str], - built: &PathBuiltFromString, - cwd: &Path, + built_paths: &[PathBuiltFromString], options: &CompletionOptions, want_directory: bool, isdir: bool, ) -> Vec { - let mut completions = vec![]; - if let Some((&base, rest)) = partial.split_first() { if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) { - let mut built = built.clone(); - built.parts.push(base.to_string()); - built.isdir = true; - return complete_rec(rest, &built, cwd, options, want_directory, isdir); - } - } - - let mut built_path = cwd.to_path_buf(); - for part in &built.parts { - built_path.push(part); - } - - let Ok(result) = built_path.read_dir() else { - return completions; - }; - - let mut entries = Vec::new(); - for entry in result.filter_map(|e| e.ok()) { - let entry_name = entry.file_name().to_string_lossy().into_owned(); - let entry_isdir = entry.path().is_dir(); - let mut built = built.clone(); - built.parts.push(entry_name.clone()); - built.isdir = entry_isdir; - - if !want_directory || entry_isdir { - entries.push((entry_name, built)); + let built_paths: Vec<_> = built_paths + .iter() + .map(|built| { + let mut built = built.clone(); + built.parts.push(base.to_string()); + built.isdir = true; + built + }) + .collect(); + return complete_rec(rest, &built_paths, options, want_directory, isdir); } } let prefix = partial.first().unwrap_or(&""); - let sorted_entries = sort_completions(prefix, entries, options, |(entry, _)| entry); + let mut matcher = NuMatcher::new(prefix, options.clone()); - for (entry_name, built) in sorted_entries { + for built in built_paths { + let mut path = built.cwd.clone(); + for part in &built.parts { + path.push(part); + } + + let Ok(result) = path.read_dir() else { + continue; + }; + + for entry in result.filter_map(|e| e.ok()) { + let entry_name = entry.file_name().to_string_lossy().into_owned(); + let entry_isdir = entry.path().is_dir(); + let mut built = built.clone(); + built.parts.push(entry_name.clone()); + built.isdir = entry_isdir; + + if !want_directory || entry_isdir { + matcher.add(entry_name.clone(), (entry_name, built)); + } + } + } + + let mut completions = vec![]; + for (entry_name, built) in matcher.results() { match partial.split_first() { Some((base, rest)) => { - if matches(base, &entry_name, options) { - // We use `isdir` to confirm that the current component has - // at least one next component or a slash. - // Serves as confirmation to ignore longer completions for - // components in between. - if !rest.is_empty() || isdir { - completions.extend(complete_rec( - rest, - &built, - cwd, - options, - want_directory, - isdir, - )); - } else { - completions.push(built); - } + // We use `isdir` to confirm that the current component has + // at least one next component or a slash. + // Serves as confirmation to ignore longer completions for + // components in between. + if !rest.is_empty() || isdir { + completions.extend(complete_rec( + rest, + &[built], + options, + want_directory, + isdir, + )); + } else { + completions.push(built); } - if entry_name.eq(base) - && matches!(options.match_algorithm, MatchAlgorithm::Prefix) - && isdir - { - break; + + // For https://github.com/nushell/nushell/issues/13204 + if isdir && options.match_algorithm == MatchAlgorithm::Prefix { + let exact_match = if options.case_sensitive { + entry_name.eq(base) + } else { + entry_name.to_folded_case().eq(&base.to_folded_case()) + }; + if exact_match { + break; + } } } None => { @@ -147,15 +153,25 @@ fn surround_remove(partial: &str) -> String { partial.to_string() } +pub struct FileSuggestion { + pub span: nu_protocol::Span, + pub path: String, + pub style: Option