From 7c84634e3fae69c3daa4d65e4381a80516bc13d9 Mon Sep 17 00:00:00 2001 From: Solomon Date: Sat, 23 Nov 2024 14:42:00 -0700 Subject: [PATCH 01/99] return accurate type errors from blocks/expressions in type unions (#14420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # User-Facing Changes - `expected ` errors are now propagated from `Closure | Block | Expression` instead of falling back to "expected one of..." for the block: Before: ```nushell def foo [bar: bool] {} if true {} else { foo 1 } ────┬──── ╰── expected one of a list of accepted shapes: [Block, Expression] ``` After: ```nushell if true {} else { foo 1 } ┬ ╰── expected bool ``` --- crates/nu-parser/src/parser.rs | 119 ++++++++++++++------------------- tests/repl/test_type_check.rs | 18 +++++ 2 files changed, 67 insertions(+), 70 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7db2e112bf..4048bec2a9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -772,6 +772,51 @@ fn calculate_end_span( } } +fn parse_oneof( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, + possible_shapes: &Vec, + multispan: bool, +) -> Expression { + for shape in possible_shapes { + let starting_error_count = working_set.parse_errors.len(); + let value = match multispan { + true => parse_multispan_value(working_set, spans, spans_idx, shape), + false => parse_value(working_set, spans[*spans_idx], shape), + }; + + if starting_error_count == working_set.parse_errors.len() { + return value; + } + + // while trying the possible shapes, ignore Expected type errors + // unless they're inside a block, closure, or expression + let propagate_error = match working_set.parse_errors.last() { + Some(ParseError::Expected(_, error_span)) + | Some(ParseError::ExpectedWithStringMsg(_, error_span)) => { + matches!( + shape, + SyntaxShape::Block | SyntaxShape::Closure(_) | SyntaxShape::Expression + ) && *error_span != spans[*spans_idx] + } + _ => true, + }; + if !propagate_error { + working_set.parse_errors.truncate(starting_error_count); + } + } + + if working_set.parse_errors.is_empty() { + working_set.error(ParseError::ExpectedWithStringMsg( + format!("one of a list of accepted shapes: {possible_shapes:?}"), + spans[*spans_idx], + )); + } + + Expression::garbage(working_set, spans[*spans_idx]) +} + pub fn parse_multispan_value( working_set: &mut StateWorkingSet, spans: &[Span], @@ -800,54 +845,10 @@ pub fn parse_multispan_value( arg } - SyntaxShape::OneOf(shapes) => { - // handle for `if` command. - //let block_then_exp = shapes.as_slice() == [SyntaxShape::Block, SyntaxShape::Expression]; - for shape in shapes.iter() { - let starting_error_count = working_set.parse_errors.len(); - let s = parse_multispan_value(working_set, spans, spans_idx, shape); - - if starting_error_count == working_set.parse_errors.len() { - return s; - } else if let Some( - ParseError::Expected(..) | ParseError::ExpectedWithStringMsg(..), - ) = working_set.parse_errors.last() - { - working_set.parse_errors.truncate(starting_error_count); - continue; - } - // `if` is parsing block first and then expression. - // when we're writing something like `else if $a`, parsing as a - // block will result to error(because it's not a block) - // - // If parse as a expression also failed, user is more likely concerned - // about expression failure rather than "expect block failure"". - - // FIXME FIXME FIXME - // if block_then_exp { - // match &err { - // Some(ParseError::Expected(expected, _)) => { - // if expected.starts_with("block") { - // err = e - // } - // } - // _ => err = err.or(e), - // } - // } else { - // err = err.or(e) - // } - } - let span = spans[*spans_idx]; - - if working_set.parse_errors.is_empty() { - working_set.error(ParseError::ExpectedWithStringMsg( - format!("one of a list of accepted shapes: {shapes:?}"), - span, - )); - } - - Expression::garbage(working_set, span) + SyntaxShape::OneOf(possible_shapes) => { + parse_oneof(working_set, spans, spans_idx, possible_shapes, true) } + SyntaxShape::Expression => { trace!("parsing: expression"); @@ -4826,29 +4827,7 @@ pub fn parse_value( SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span), SyntaxShape::OneOf(possible_shapes) => { - for s in possible_shapes { - let starting_error_count = working_set.parse_errors.len(); - let value = parse_value(working_set, span, s); - - if starting_error_count == working_set.parse_errors.len() { - return value; - } else if let Some( - ParseError::Expected(..) | ParseError::ExpectedWithStringMsg(..), - ) = working_set.parse_errors.last() - { - working_set.parse_errors.truncate(starting_error_count); - continue; - } - } - - if working_set.parse_errors.is_empty() { - working_set.error(ParseError::ExpectedWithStringMsg( - format!("one of a list of accepted shapes: {possible_shapes:?}"), - span, - )); - } - - Expression::garbage(working_set, span) + parse_oneof(working_set, &[span], &mut 0, possible_shapes, false) } SyntaxShape::Any => { diff --git a/tests/repl/test_type_check.rs b/tests/repl/test_type_check.rs index 95ef7f4323..07a8d99495 100644 --- a/tests/repl/test_type_check.rs +++ b/tests/repl/test_type_check.rs @@ -1,4 +1,5 @@ use crate::repl::tests::{fail_test, run_test, TestResult}; +use rstest::rstest; #[test] fn chained_operator_typecheck() -> TestResult { @@ -154,3 +155,20 @@ fn in_variable_expression_wrong_output_type() -> TestResult { "expected int", ) } + +#[rstest] +#[case("if true {} else { foo 1 }")] +#[case("if true {} else if (foo 1) == null { }")] +#[case("match 1 { 0 => { foo 1 } }")] +#[case("try { } catch { foo 1 }")] +/// type errors should propagate from `OneOf(Block | Closure | Expression, ..)` +fn in_oneof_block_expected_type(#[case] input: &str) -> TestResult { + let def = "def foo [bar: bool] {};"; + + fail_test(&format!("{def} {input}"), "expected bool") +} + +#[test] +fn in_oneof_block_expected_block() -> TestResult { + fail_test("match 1 { 0 => { try 3 } }", "expected block") +} From 58576630dbee46474a25e788cee7bc3737c8bf94 Mon Sep 17 00:00:00 2001 From: Beinsezii <39478211+Beinsezii@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:49:25 -0800 Subject: [PATCH 02/99] command/http/client use CRLF for headers join instead of LF (#14417) # Description Apparently it should be joint CRLF for the EOL marker https://www.rfc-editor.org/rfc/rfc2616#section-2.2 Plain LF isn't particularly standardized and many backends don't recognize it. Tested on `starlette` # User-Facing Changes None # Tests + Formatting It's two characters; everything passes # After Submitting Not needed --- crates/nu-command/src/network/http/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index 62dd693627..504cc7b632 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -376,7 +376,7 @@ fn send_multipart_request( format!("Content-Length: {}", val.len()), ]; builder - .add(&mut Cursor::new(val), &headers.join("\n")) + .add(&mut Cursor::new(val), &headers.join("\r\n")) .map_err(err)?; } else { let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col); From 83d8e936ad1aade55050f9ed5c7c198c15877a94 Mon Sep 17 00:00:00 2001 From: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sat, 23 Nov 2024 17:04:27 -0500 Subject: [PATCH 03/99] Fix small typos in std/dirs (#14422) # Description Typos in the command doc-help. --- crates/nu-std/std/dirs/mod.nu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-std/std/dirs/mod.nu b/crates/nu-std/std/dirs/mod.nu index a26128a5d7..ab3403f36c 100644 --- a/crates/nu-std/std/dirs/mod.nu +++ b/crates/nu-std/std/dirs/mod.nu @@ -43,7 +43,7 @@ export def --env add [ } # Make the next directory on the list the active directory. -# If the currenta ctive directory is the last in the list, +# If the current active directory is the last in the list, # then cycle to the top of the list. export def --env next [ N:int = 1 # number of positions to move. @@ -52,7 +52,7 @@ export def --env next [ } # Make the previous directory on the list the active directory. -# If the current active directory is the first in the list, +# If the current active directory is the first in the list, # then cycle to the end of the list. export def --env prev [ N:int = 1 # number of positions to move. From dd3a3a2717650113d5e1986fa06feebbef9a1ef0 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:37:12 -0600 Subject: [PATCH 04/99] remove `terminal_size` crate everywhere it makes sense (#14423) # Description This PR removes the `terminal_size` crate everywhere that it made sense. I replaced it with crossterm's version called `size`. The places I didn't remove it were the places that did not have a dependency on crossterm. So, I thought it was "cheaper" to have a dep on term_size vs crossterm in those locations. --- Cargo.lock | 2 -- crates/nu-command/Cargo.toml | 1 - crates/nu-command/src/debug/inspect.rs | 9 +++------ crates/nu-command/src/platform/term_size.rs | 11 ++++------- crates/nu-command/src/viewers/griddle.rs | 4 ++-- crates/nu-command/src/viewers/table.rs | 4 ++-- crates/nu-explore/Cargo.toml | 1 - crates/nu-explore/src/lib.rs | 4 ++-- 8 files changed, 13 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d15ecdf729..e57b9da8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3358,7 +3358,6 @@ dependencies = [ "sysinfo 0.32.0", "tabled", "tempfile", - "terminal_size 0.4.0", "titlecase", "toml 0.8.19", "trash", @@ -3427,7 +3426,6 @@ dependencies = [ "nu-utils", "ratatui", "strip-ansi-escapes", - "terminal_size 0.4.0", "unicode-width 0.1.11", ] diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c37954e9c1..53e39353c0 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -86,7 +86,6 @@ serde_yaml = { workspace = true } sha2 = { workspace = true } sysinfo = { workspace = true } tabled = { workspace = true, features = ["ansi"], default-features = false } -terminal_size = { workspace = true } titlecase = { workspace = true } toml = { workspace = true, features = ["preserve_order"] } unicode-segmentation = { workspace = true } diff --git a/crates/nu-command/src/debug/inspect.rs b/crates/nu-command/src/debug/inspect.rs index 97217780bf..4a9f7bc4a5 100644 --- a/crates/nu-command/src/debug/inspect.rs +++ b/crates/nu-command/src/debug/inspect.rs @@ -1,6 +1,6 @@ use super::inspect_table; +use crossterm::terminal::size; use nu_engine::command_prelude::*; -use terminal_size::{terminal_size, Height, Width}; #[derive(Clone)] pub struct Inspect; @@ -38,12 +38,9 @@ impl Command for Inspect { let original_input = input_val.clone(); let description = input_val.get_type().to_string(); - let (cols, _rows) = match terminal_size() { - Some((w, h)) => (Width(w.0), Height(h.0)), - None => (Width(0), Height(0)), - }; + let (cols, _rows) = size().unwrap_or((0, 0)); - let table = inspect_table::build_table(input_val, description, cols.0 as usize); + let table = inspect_table::build_table(input_val, description, cols as usize); // Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell // tabular output. If we printed to stdout, nushell would get confused with two outputs. diff --git a/crates/nu-command/src/platform/term_size.rs b/crates/nu-command/src/platform/term_size.rs index a4261dee81..b41d9da6c1 100644 --- a/crates/nu-command/src/platform/term_size.rs +++ b/crates/nu-command/src/platform/term_size.rs @@ -1,5 +1,5 @@ +use crossterm::terminal::size; use nu_engine::command_prelude::*; -use terminal_size::{terminal_size, Height, Width}; #[derive(Clone)] pub struct TermSize; @@ -51,15 +51,12 @@ impl Command for TermSize { ) -> Result { let head = call.head; - let (cols, rows) = match terminal_size() { - Some((w, h)) => (Width(w.0), Height(h.0)), - None => (Width(0), Height(0)), - }; + let (cols, rows) = size().unwrap_or((0, 0)); Ok(Value::record( record! { - "columns" => Value::int(cols.0 as i64, head), - "rows" => Value::int(rows.0 as i64, head), + "columns" => Value::int(cols as i64, head), + "rows" => Value::int(rows as i64, head), }, head, ) diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 2774903d0f..730c5ec0df 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -1,12 +1,12 @@ // use super::icons::{icon_for_file, iconify_style_ansi_to_nu}; use super::icons::icon_for_file; +use crossterm::terminal::size; use lscolors::Style; use nu_engine::{command_prelude::*, env_to_string}; use nu_protocol::Config; use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; use nu_utils::get_ls_colors; use std::path::Path; -use terminal_size::{Height, Width}; #[derive(Clone)] pub struct Griddle; @@ -192,7 +192,7 @@ fn create_grid_output( let cols = if let Some(col) = width_param { col as u16 - } else if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() { + } else if let Ok((w, _h)) = size() { w } else { 80u16 diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 50db949ced..43c9ed0b8d 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -2,6 +2,7 @@ // overall reduce the redundant calls to StyleComputer etc. // the goal is to configure it once... +use crossterm::terminal::size; use lscolors::{LsColors, Style}; use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_engine::{command_prelude::*, env_to_string}; @@ -22,7 +23,6 @@ use std::{ str::FromStr, time::Instant, }; -use terminal_size::{Height, Width}; use url::Url; const STREAM_PAGE_SIZE: usize = 1000; @@ -30,7 +30,7 @@ const STREAM_PAGE_SIZE: usize = 1000; fn get_width_param(width_param: Option) -> usize { if let Some(col) = width_param { col as usize - } else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() { + } else if let Ok((w, _h)) = size() { w as usize } else { 80 diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 6ea09e3f13..e201262750 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -27,7 +27,6 @@ nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" } anyhow = { workspace = true } log = { workspace = true } -terminal_size = { workspace = true } strip-ansi-escapes = { workspace = true } crossterm = { workspace = true } ratatui = { workspace = true } diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index d645572c89..a6d4161631 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -9,6 +9,7 @@ mod views; use anyhow::Result; use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; +use crossterm::terminal::size; pub use default_context::add_explore_context; pub use explore::Explore; use explore::ExploreConfig; @@ -19,7 +20,6 @@ use nu_protocol::{ }; use pager::{Page, Pager, PagerConfig}; use registry::CommandRegistry; -use terminal_size::{Height, Width}; use views::{BinaryView, Orientation, Preview, RecordView}; mod util { @@ -80,7 +80,7 @@ fn create_record_view( } if config.tail { - if let Some((Width(w), Height(h))) = terminal_size::terminal_size() { + if let Ok((w, h)) = size() { view.tail(w, h); } } From 4d3283e23555610bd5443e1e57afdd5f748d25dc Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 24 Nov 2024 10:59:54 -0800 Subject: [PATCH 05/99] Change append operator to concatenation operator (#14344) # Description The "append" operator currently serves as both the append operator and the concatenation operator. This dual role creates ambiguity when operating on nested lists. ```nu [1 2] ++ 3 # appends a value to a list [1 2 3] [1 2] ++ [3 4] # concatenates two lists [1 2 3 4] [[1 2] [3 4]] ++ [5 6] # does this give [[1 2] [3 4] [5 6]] # or [[1 2] [3 4] 5 6] ``` Another problem is that `++=` can change the type of a variable: ```nu mut str = 'hello ' $str ++= ['world'] ($str | describe) == list ``` Note that appending is only relevant for lists, but concatenation is relevant for lists, strings, and binary values. Additionally, appending can be expressed in terms of concatenation (see example below). So, this PR changes the `++` operator to only perform concatenation. # User-Facing Changes Using the `++` operator with a list and a non-list value will now be a compile time or runtime error. ```nu mut list = [] $list ++= 1 # error ``` Instead, concatenate a list with one element: ```nu $list ++= [1] ``` Or use `append`: ```nu $list = $list | append 1 ``` # After Submitting Update book and docs. --------- Co-authored-by: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> --- .../src/completions/operator_completions.rs | 18 +--- crates/nu-command/src/help/help_operators.rs | 12 +-- .../commands/assignment/append_assign.rs | 88 ------------------- .../tests/commands/assignment/concat.rs | 76 ++++++++++++++++ .../tests/commands/assignment/mod.rs | 2 +- crates/nu-command/tests/commands/math/mod.rs | 30 +------ crates/nu-command/tests/commands/url/join.rs | 17 ++-- crates/nu-engine/src/compile/operator.rs | 2 +- crates/nu-engine/src/eval.rs | 4 +- crates/nu-engine/src/eval_ir.rs | 2 +- crates/nu-parser/src/parser.rs | 4 +- crates/nu-parser/src/type_check.rs | 22 ++--- .../nu-plugin-engine/src/interface/tests.rs | 4 +- crates/nu-protocol/src/ast/operator.rs | 10 +-- crates/nu-protocol/src/eval_base.rs | 2 +- crates/nu-protocol/src/value/mod.rs | 30 ++----- crates/nu-std/std/help/mod.nu | 4 +- .../src/cool_custom_value.rs | 2 +- tests/const_/mod.rs | 1 - tests/eval/mod.rs | 2 +- 20 files changed, 131 insertions(+), 201 deletions(-) delete mode 100644 crates/nu-command/tests/commands/assignment/append_assign.rs create mode 100644 crates/nu-command/tests/commands/assignment/concat.rs diff --git a/crates/nu-cli/src/completions/operator_completions.rs b/crates/nu-cli/src/completions/operator_completions.rs index 0ce4e02e17..aebb176b76 100644 --- a/crates/nu-cli/src/completions/operator_completions.rs +++ b/crates/nu-cli/src/completions/operator_completions.rs @@ -60,10 +60,6 @@ impl Completer for OperatorCompletion { ("bit-shr", "Bitwise shift right"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::String(_) => vec![ ("=~", "Contains regex match"), @@ -72,7 +68,7 @@ impl Completer for OperatorCompletion { ("not-like", "Does not contain regex match"), ( "++", - "Appends two lists, a list and a value, two strings, or two binary values", + "Concatenates two lists, two strings, or two binary values", ), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), @@ -95,10 +91,6 @@ impl Completer for OperatorCompletion { ("**", "Power of"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::Bool(_) => vec![ ( @@ -113,15 +105,11 @@ impl Completer for OperatorCompletion { ("not", "Negates a value or expression"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::FullCellPath(path) => match path.head.expr { Expr::List(_) => vec![( "++", - "Appends two lists, a list and a value, two strings, or two binary values", + "Concatenates two lists, two strings, or two binary values", )], Expr::Var(id) => get_variable_completions(id, working_set), _ => vec![], @@ -161,7 +149,7 @@ pub fn get_variable_completions<'a>( Type::List(_) | Type::String | Type::Binary => vec![ ( "++=", - "Appends a list, a value, a string, or a binary value to a variable.", + "Concatenates two lists, two strings, or two binary values", ), ("=", "Assigns a value to a variable."), ], diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index 28be6a68ed..28bb223e61 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -31,7 +31,7 @@ impl Command for HelpOperators { let mut operators = [ Operator::Assignment(Assignment::Assign), Operator::Assignment(Assignment::PlusAssign), - Operator::Assignment(Assignment::AppendAssign), + Operator::Assignment(Assignment::ConcatAssign), Operator::Assignment(Assignment::MinusAssign), Operator::Assignment(Assignment::MultiplyAssign), Operator::Assignment(Assignment::DivideAssign), @@ -48,7 +48,7 @@ impl Command for HelpOperators { Operator::Comparison(Comparison::StartsWith), Operator::Comparison(Comparison::EndsWith), Operator::Math(Math::Plus), - Operator::Math(Math::Append), + Operator::Math(Math::Concat), Operator::Math(Math::Minus), Operator::Math(Math::Multiply), Operator::Math(Math::Divide), @@ -144,8 +144,8 @@ fn description(operator: &Operator) -> &'static str { Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.", Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.", Operator::Math(Math::Plus) => "Adds two values.", - Operator::Math(Math::Append) => { - "Appends two lists, a list and a value, two strings, or two binary values." + Operator::Math(Math::Concat) => { + "Concatenates two lists, two strings, or two binary values." } Operator::Math(Math::Minus) => "Subtracts two values.", Operator::Math(Math::Multiply) => "Multiplies two values.", @@ -163,8 +163,8 @@ fn description(operator: &Operator) -> &'static str { Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.", Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.", Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.", - Operator::Assignment(Assignment::AppendAssign) => { - "Appends a list, a value, a string, or a binary value to a variable." + Operator::Assignment(Assignment::ConcatAssign) => { + "Concatenates two lists, two strings, or two binary values." } Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.", Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.", diff --git a/crates/nu-command/tests/commands/assignment/append_assign.rs b/crates/nu-command/tests/commands/assignment/append_assign.rs deleted file mode 100644 index 97abc8eb6f..0000000000 --- a/crates/nu-command/tests/commands/assignment/append_assign.rs +++ /dev/null @@ -1,88 +0,0 @@ -use nu_test_support::nu; - -#[test] -fn append_assign_int() { - let actual = nu!(r#" - mut a = [1 2]; - $a ++= [3 4]; - $a == [1 2 3 4] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_string() { - let actual = nu!(r#" - mut a = [a b]; - $a ++= [c d]; - $a == [a b c d] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_any() { - let actual = nu!(r#" - mut a = [1 2 a]; - $a ++= [b 3]; - $a == [1 2 a b 3] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_both_empty() { - let actual = nu!(r#" - mut a = []; - $a ++= []; - $a == [] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_type_mismatch() { - let actual = nu!(r#" - mut a = [1 2]; - $a ++= [a]; - $a == [1 2 "a"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_single_element() { - let actual = nu!(r#" - mut a = ["list" "and"]; - $a ++= "a single element"; - $a == ["list" "and" "a single element"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_to_single_element() { - let actual = nu!(r#" - mut a = "string"; - $a ++= ["and" "the" "list"]; - $a == ["string" "and" "the" "list"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_single_to_single() { - let actual = nu!(r#" - mut a = 1; - $a ++= "and a single element"; - "#); - - assert!(actual.err.contains("nu::parser::unsupported_operation")); -} diff --git a/crates/nu-command/tests/commands/assignment/concat.rs b/crates/nu-command/tests/commands/assignment/concat.rs new file mode 100644 index 0000000000..603a486f14 --- /dev/null +++ b/crates/nu-command/tests/commands/assignment/concat.rs @@ -0,0 +1,76 @@ +use nu_test_support::nu; + +#[test] +fn concat_assign_list_int() { + let actual = nu!(r#" + mut a = [1 2]; + $a ++= [3 4]; + $a == [1 2 3 4] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_list_string() { + let actual = nu!(r#" + mut a = [a b]; + $a ++= [c d]; + $a == [a b c d] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_any() { + let actual = nu!(r#" + mut a = [1 2 a]; + $a ++= [b 3]; + $a == [1 2 a b 3] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_both_empty() { + let actual = nu!(r#" + mut a = []; + $a ++= []; + $a == [] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_string() { + let actual = nu!(r#" + mut a = 'hello'; + $a ++= ' world'; + $a == 'hello world' + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_type_mismatch() { + let actual = nu!(r#" + mut a = []; + $a ++= 'str' + "#); + + assert!(actual.err.contains("nu::parser::unsupported_operation")); +} + +#[test] +fn concat_assign_runtime_type_mismatch() { + let actual = nu!(r#" + mut a = []; + $a ++= if true { 'str' } + "#); + + assert!(actual.err.contains("nu::shell::type_mismatch")); +} diff --git a/crates/nu-command/tests/commands/assignment/mod.rs b/crates/nu-command/tests/commands/assignment/mod.rs index 8235f532f5..0859deee1a 100644 --- a/crates/nu-command/tests/commands/assignment/mod.rs +++ b/crates/nu-command/tests/commands/assignment/mod.rs @@ -1 +1 @@ -mod append_assign; +mod concat; diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs index cde6a2da35..5d04f450a4 100644 --- a/crates/nu-command/tests/commands/math/mod.rs +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -483,7 +483,7 @@ fn compound_where_paren() { // TODO: these ++ tests are not really testing *math* functionality, maybe find another place for them #[test] -fn adding_lists() { +fn concat_lists() { let actual = nu!(pipeline( r#" [1 3] ++ [5 6] | to nuon @@ -494,29 +494,7 @@ fn adding_lists() { } #[test] -fn adding_list_and_value() { - let actual = nu!(pipeline( - r#" - [1 3] ++ 5 | to nuon - "# - )); - - assert_eq!(actual.out, "[1, 3, 5]"); -} - -#[test] -fn adding_value_and_list() { - let actual = nu!(pipeline( - r#" - 1 ++ [3 5] | to nuon - "# - )); - - assert_eq!(actual.out, "[1, 3, 5]"); -} - -#[test] -fn adding_tables() { +fn concat_tables() { let actual = nu!(pipeline( r#" [[a b]; [1 2]] ++ [[c d]; [10 11]] | to nuon @@ -526,7 +504,7 @@ fn adding_tables() { } #[test] -fn append_strings() { +fn concat_strings() { let actual = nu!(pipeline( r#" "foo" ++ "bar" @@ -536,7 +514,7 @@ fn append_strings() { } #[test] -fn append_binary_values() { +fn concat_binary_values() { let actual = nu!(pipeline( r#" 0x[01 02] ++ 0x[03 04] | to nuon diff --git a/crates/nu-command/tests/commands/url/join.rs b/crates/nu-command/tests/commands/url/join.rs index 92257a41ff..086d230cc4 100644 --- a/crates/nu-command/tests/commands/url/join.rs +++ b/crates/nu-command/tests/commands/url/join.rs @@ -27,7 +27,7 @@ fn url_join_with_only_user() { "password": "", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -44,7 +44,7 @@ fn url_join_with_only_pwd() { "password": "pwd", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -61,7 +61,7 @@ fn url_join_with_user_and_pwd() { "password": "pwd", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -79,7 +79,7 @@ fn url_join_with_query() { "host": "localhost", "query": "par_1=aaa&par_2=bbb" "port": "", - } | url join + } | url join "# )); @@ -411,12 +411,9 @@ fn url_join_with_params_invalid_table() { "host": "localhost", "params": ( [ - ["key", "value"]; - ["par_1", "aaa"], - ["par_2", "bbb"], - ["par_1", "ccc"], - ["par_2", "ddd"], - ] ++ ["not a record"] + { key: foo, value: bar } + "not a record" + ] ), "port": "1234", } | url join diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs index bad206358d..0f7059e027 100644 --- a/crates/nu-engine/src/compile/operator.rs +++ b/crates/nu-engine/src/compile/operator.rs @@ -155,7 +155,7 @@ pub(crate) fn decompose_assignment(assignment: Assignment) -> Option { match assignment { Assignment::Assign => None, Assignment::PlusAssign => Some(Operator::Math(Math::Plus)), - Assignment::AppendAssign => Some(Operator::Math(Math::Append)), + Assignment::ConcatAssign => Some(Operator::Math(Math::Concat)), Assignment::MinusAssign => Some(Operator::Math(Math::Minus)), Assignment::MultiplyAssign => Some(Operator::Math(Math::Multiply)), Assignment::DivideAssign => Some(Operator::Math(Math::Divide)), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index fc4a88f83e..0e3917290f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -547,9 +547,9 @@ impl Eval for EvalRuntime { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.div(op_span, &rhs, op_span)? } - Assignment::AppendAssign => { + Assignment::ConcatAssign => { let lhs = eval_expression::(engine_state, stack, lhs)?; - lhs.append(op_span, &rhs, op_span)? + lhs.concat(op_span, &rhs, op_span)? } }; diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 8d809f182e..1aec6b30ea 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -956,7 +956,7 @@ fn binary_op( }, Operator::Math(mat) => match mat { Math::Plus => lhs_val.add(op_span, &rhs_val, span)?, - Math::Append => lhs_val.append(op_span, &rhs_val, span)?, + Math::Concat => lhs_val.concat(op_span, &rhs_val, span)?, Math::Minus => lhs_val.sub(op_span, &rhs_val, span)?, Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?, Math::Divide => lhs_val.div(op_span, &rhs_val, span)?, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 4048bec2a9..d9f534cc60 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4887,7 +4887,7 @@ pub fn parse_assignment_operator(working_set: &mut StateWorkingSet, span: Span) let operator = match contents { b"=" => Operator::Assignment(Assignment::Assign), b"+=" => Operator::Assignment(Assignment::PlusAssign), - b"++=" => Operator::Assignment(Assignment::AppendAssign), + b"++=" => Operator::Assignment(Assignment::ConcatAssign), b"-=" => Operator::Assignment(Assignment::MinusAssign), b"*=" => Operator::Assignment(Assignment::MultiplyAssign), b"/=" => Operator::Assignment(Assignment::DivideAssign), @@ -5013,7 +5013,7 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi b"=~" | b"like" => Operator::Comparison(Comparison::RegexMatch), b"!~" | b"not-like" => Operator::Comparison(Comparison::NotRegexMatch), b"+" => Operator::Math(Math::Plus), - b"++" => Operator::Math(Math::Append), + b"++" => Operator::Math(Math::Concat), b"-" => Operator::Math(Math::Minus), b"*" => Operator::Math(Math::Multiply), b"/" => Operator::Math(Math::Divide), diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index a7de3800e2..e5f2a22921 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -132,7 +132,7 @@ pub fn math_result_type( ) } }, - Operator::Math(Math::Append) => check_append(working_set, lhs, rhs, op), + Operator::Math(Math::Concat) => check_concat(working_set, lhs, rhs, op), Operator::Math(Math::Minus) => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), @@ -935,8 +935,8 @@ pub fn math_result_type( ) } }, - Operator::Assignment(Assignment::AppendAssign) => { - check_append(working_set, lhs, rhs, op) + Operator::Assignment(Assignment::ConcatAssign) => { + check_concat(working_set, lhs, rhs, op) } Operator::Assignment(_) => match (&lhs.ty, &rhs.ty) { (x, y) if x == y => (Type::Nothing, None), @@ -1085,7 +1085,7 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> output_errors } -fn check_append( +fn check_concat( working_set: &mut StateWorkingSet, lhs: &Expression, rhs: &Expression, @@ -1099,23 +1099,17 @@ fn check_append( (Type::List(Box::new(Type::Any)), None) } } - (Type::List(a), b) | (b, Type::List(a)) => { - if a == &Box::new(b.clone()) { - (Type::List(a.clone()), None) - } else { - (Type::List(Box::new(Type::Any)), None) - } - } (Type::Table(a), Type::Table(_)) => (Type::Table(a.clone()), None), (Type::String, Type::String) => (Type::String, None), (Type::Binary, Type::Binary) => (Type::Binary, None), (Type::Any, _) | (_, Type::Any) => (Type::Any, None), - (Type::Table(_) | Type::String | Type::Binary, _) => { + (Type::Table(_) | Type::List(_) | Type::String | Type::Binary, _) + | (_, Type::Table(_) | Type::List(_) | Type::String | Type::Binary) => { *op = Expression::garbage(working_set, op.span); ( Type::Any, Some(ParseError::UnsupportedOperationRHS( - "append".into(), + "concatenation".into(), op.span, lhs.span, lhs.ty.clone(), @@ -1129,7 +1123,7 @@ fn check_append( ( Type::Any, Some(ParseError::UnsupportedOperationLHS( - "append".into(), + "concatenation".into(), op.span, lhs.span, lhs.ty.clone(), diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index 5058a45695..4761da6824 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -1485,7 +1485,7 @@ fn prepare_plugin_call_custom_value_op() { span, }, CustomValueOp::Operation( - Operator::Math(Math::Append).into_spanned(span), + Operator::Math(Math::Concat).into_spanned(span), cv_ok_val.clone(), ), ), @@ -1498,7 +1498,7 @@ fn prepare_plugin_call_custom_value_op() { span, }, CustomValueOp::Operation( - Operator::Math(Math::Append).into_spanned(span), + Operator::Math(Math::Concat).into_spanned(span), cv_bad_val.clone(), ), ), diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 8c2e59d93a..bde866498c 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -24,7 +24,7 @@ pub enum Comparison { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Math { Plus, - Append, + Concat, Minus, Multiply, Divide, @@ -53,7 +53,7 @@ pub enum Bits { pub enum Assignment { Assign, PlusAssign, - AppendAssign, + ConcatAssign, MinusAssign, MultiplyAssign, DivideAssign, @@ -90,7 +90,7 @@ impl Operator { | Self::Comparison(Comparison::NotEqual) | Self::Comparison(Comparison::In) | Self::Comparison(Comparison::NotIn) - | Self::Math(Math::Append) => 80, + | Self::Math(Math::Concat) => 80, Self::Bits(Bits::BitAnd) => 75, Self::Bits(Bits::BitXor) => 70, Self::Bits(Bits::BitOr) => 60, @@ -107,7 +107,7 @@ impl Display for Operator { match self { Operator::Assignment(Assignment::Assign) => write!(f, "="), Operator::Assignment(Assignment::PlusAssign) => write!(f, "+="), - Operator::Assignment(Assignment::AppendAssign) => write!(f, "++="), + Operator::Assignment(Assignment::ConcatAssign) => write!(f, "++="), Operator::Assignment(Assignment::MinusAssign) => write!(f, "-="), Operator::Assignment(Assignment::MultiplyAssign) => write!(f, "*="), Operator::Assignment(Assignment::DivideAssign) => write!(f, "/="), @@ -124,7 +124,7 @@ impl Display for Operator { Operator::Comparison(Comparison::In) => write!(f, "in"), Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"), Operator::Math(Math::Plus) => write!(f, "+"), - Operator::Math(Math::Append) => write!(f, "++"), + Operator::Math(Math::Concat) => write!(f, "++"), Operator::Math(Math::Minus) => write!(f, "-"), Operator::Math(Math::Multiply) => write!(f, "*"), Operator::Math(Math::Divide) => write!(f, "/"), diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 12070f9ca3..0a65c50817 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -238,7 +238,7 @@ pub trait Eval { Math::Minus => lhs.sub(op_span, &rhs, expr_span), Math::Multiply => lhs.mul(op_span, &rhs, expr_span), Math::Divide => lhs.div(op_span, &rhs, expr_span), - Math::Append => lhs.append(op_span, &rhs, expr_span), + Math::Concat => lhs.concat(op_span, &rhs, expr_span), Math::Modulo => lhs.modulo(op_span, &rhs, expr_span), Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr_span), Math::Pow => lhs.pow(op_span, &rhs, expr_span), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1a1e8fada2..28b468a151 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -2503,34 +2503,20 @@ impl Value { } } - pub fn append(&self, op: Span, rhs: &Value, span: Span) -> Result { + pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result { match (self, rhs) { (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => { - let mut lhs = lhs.clone(); - let mut rhs = rhs.clone(); - lhs.append(&mut rhs); - Ok(Value::list(lhs, span)) - } - (Value::List { vals: lhs, .. }, val) => { - let mut lhs = lhs.clone(); - lhs.push(val.clone()); - Ok(Value::list(lhs, span)) - } - (val, Value::List { vals: rhs, .. }) => { - let mut rhs = rhs.clone(); - rhs.insert(0, val.clone()); - Ok(Value::list(rhs, span)) + Ok(Value::list([lhs.as_slice(), rhs.as_slice()].concat(), span)) } (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { - Ok(Value::string(lhs.to_string() + rhs, span)) - } - (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => { - let mut val = lhs.clone(); - val.extend(rhs); - Ok(Value::binary(val, span)) + Ok(Value::string([lhs.as_str(), rhs.as_str()].join(""), span)) } + (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => Ok(Value::binary( + [lhs.as_slice(), rhs.as_slice()].concat(), + span, + )), (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(self.span(), Operator::Math(Math::Append), op, rhs) + lhs.operation(self.span(), Operator::Math(Math::Concat), op, rhs) } _ => Err(ShellError::OperatorMismatch { op_span: op, diff --git a/crates/nu-std/std/help/mod.nu b/crates/nu-std/std/help/mod.nu index 089bbff7f8..9f13f1bb18 100644 --- a/crates/nu-std/std/help/mod.nu +++ b/crates/nu-std/std/help/mod.nu @@ -37,7 +37,7 @@ def get-all-operators [] { return [ [Assignment, =, Assign, "Assigns a value to a variable.", 10] [Assignment, +=, PlusAssign, "Adds a value to a variable.", 10] - [Assignment, ++=, AppendAssign, "Appends a list or a value to a variable.", 10] + [Assignment, ++=, ConcatAssign, "Concatenate two lists, two strings, or two binary values.", 10] [Assignment, -=, MinusAssign, "Subtracts a value from a variable.", 10] [Assignment, *=, MultiplyAssign, "Multiplies a variable by a value.", 10] [Assignment, /=, DivideAssign, "Divides a variable by a value.", 10] @@ -55,7 +55,7 @@ def get-all-operators [] { return [ [Comparison, ends-with, EndsWith, "Checks if a string ends with another.", 80] [Comparison, not, UnaryNot, "Negates a value or expression.", 0] [Math, +, Plus, "Adds two values.", 90] - [Math, ++, Append, "Appends two lists or a list and a value.", 80] + [Math, ++, Concat, "Concatenate two lists, two strings, or two binary values.", 80] [Math, -, Minus, "Subtracts two values.", 90] [Math, *, Multiply, "Multiplies two values.", 95] [Math, /, Divide, "Divides two values.", 95] diff --git a/crates/nu_plugin_custom_values/src/cool_custom_value.rs b/crates/nu_plugin_custom_values/src/cool_custom_value.rs index ea0cf8ec92..cd160e0d0c 100644 --- a/crates/nu_plugin_custom_values/src/cool_custom_value.rs +++ b/crates/nu_plugin_custom_values/src/cool_custom_value.rs @@ -112,7 +112,7 @@ impl CustomValue for CoolCustomValue { ) -> Result { match operator { // Append the string inside `cool` - ast::Operator::Math(ast::Math::Append) => { + ast::Operator::Math(ast::Math::Concat) => { if let Some(right) = right .as_custom_value() .ok() diff --git a/tests/const_/mod.rs b/tests/const_/mod.rs index 0c4ff5d77c..a1744e1601 100644 --- a/tests/const_/mod.rs +++ b/tests/const_/mod.rs @@ -154,7 +154,6 @@ fn const_unary_operator(#[case] inp: &[&str], #[case] expect: &str) { #[case(&[r#"const x = "a" ++ "b" "#, "$x"], "ab")] #[case(&[r#"const x = [1,2] ++ [3]"#, "$x | describe"], "list")] #[case(&[r#"const x = 0x[1,2] ++ 0x[3]"#, "$x | describe"], "binary")] -#[case(&[r#"const x = 0x[1,2] ++ [3]"#, "$x | describe"], "list")] #[case(&["const x = 1 < 2", "$x"], "true")] #[case(&["const x = (3 * 200) > (2 * 100)", "$x"], "true")] #[case(&["const x = (3 * 200) < (2 * 100)", "$x"], "false")] diff --git a/tests/eval/mod.rs b/tests/eval/mod.rs index 191b19eb89..b7ad76d32d 100644 --- a/tests/eval/mod.rs +++ b/tests/eval/mod.rs @@ -159,7 +159,7 @@ fn record_spread() { #[test] fn binary_op_example() { test_eval( - "(([1 2] ++ [3 4]) == [1 2 3 4]) and (([1 2 3] ++ 4) == ([1] ++ [2 3 4]))", + "(([1 2] ++ [3 4]) == [1 2 3 4]) and (([1] ++ [2 3 4]) == [1 2 3 4])", Eq("true"), ) } From 32196cfe789f9f552cf887ed902a9da74691f5dc Mon Sep 17 00:00:00 2001 From: Bahex Date: Tue, 26 Nov 2024 00:13:11 +0300 Subject: [PATCH 06/99] Add `term query`, for querying information from terminals. (#14427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related - #10150 - https://github.com/nushell/nushell/pull/10150#issuecomment-1721238336 - #10387 - https://github.com/nushell/nushell/pull/10387#issuecomment-1722228185 # Description `term query`: a command for querying information from the terminal. Prints the `$query`, and immediately starts reading raw bytes from stdin. The standard input will be read until the `terminator` sequence is encountered. The `terminator` is not removed from the output. It also stops on Ctrl-C with an error. ``` Usage: > term query {flags} Flags: -h, --help: Display the help message for this command -t, --terminator (required parameter) : stdin will be read until this sequence is encountered Parameters: query : The query that will be printed to stdout ``` This was previously possible with `input` until #10150. `input` command's features such as cursor control, deleting input etc. are useful, but interfere with this use case. `term query` makes the following uses possible: ```nushell # get the terminal size with ansi escape codes def terminal-size [] { let response = term query (ansi size) --terminator 'R' # $response should look like this # Length: 9 (0x9) bytes | printable whitespace ascii_other non_ascii # 00000000: 1b 5b 33 38 3b 31 35 30 52 •[38;150R let sz = $response | bytes at 2..<-1 | decode # 38;150 # $sz should look like 38;150 let size = ($sz | split row ';' | each {into int}) # output in record syntax { rows: $size.0 columns: $size.1 } } ``` ```nushell # read clipboard content using OSC 52 term query $"(ansi --osc '52;c;?')(ansi st)" --terminator (ansi st) | bytes at 7..<-2 | decode | decode base64 | decode ``` # User-Facing Changes - added `ansi query` # Tests + Formatting - Integration tests should be added if possible. --- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/platform/mod.rs | 4 +- crates/nu-command/src/platform/term/mod.rs | 7 + crates/nu-command/src/platform/term/term_.rs | 34 +++++ .../src/platform/term/term_query.rs | 137 ++++++++++++++++++ .../src/platform/{ => term}/term_size.rs | 0 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/platform/term/mod.rs create mode 100644 crates/nu-command/src/platform/term/term_.rs create mode 100644 crates/nu-command/src/platform/term/term_query.rs rename crates/nu-command/src/platform/{ => term}/term_size.rs (100%) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index cbbee717d4..9903eed774 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -248,7 +248,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { IsTerminal, Kill, Sleep, + Term, TermSize, + TermQuery, Whoami, }; diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 16a245f87b..25df4e0c59 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -5,7 +5,7 @@ mod input; mod is_terminal; mod kill; mod sleep; -mod term_size; +mod term; #[cfg(unix)] mod ulimit; mod whoami; @@ -19,7 +19,7 @@ pub use input::InputListen; pub use is_terminal::IsTerminal; pub use kill::Kill; pub use sleep::Sleep; -pub use term_size::TermSize; +pub use term::{Term, TermQuery, TermSize}; #[cfg(unix)] pub use ulimit::ULimit; pub use whoami::Whoami; diff --git a/crates/nu-command/src/platform/term/mod.rs b/crates/nu-command/src/platform/term/mod.rs new file mode 100644 index 0000000000..cd37a0583b --- /dev/null +++ b/crates/nu-command/src/platform/term/mod.rs @@ -0,0 +1,7 @@ +mod term_; +mod term_query; +mod term_size; + +pub use term_::Term; +pub use term_query::TermQuery; +pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/term/term_.rs b/crates/nu-command/src/platform/term/term_.rs new file mode 100644 index 0000000000..b28f226263 --- /dev/null +++ b/crates/nu-command/src/platform/term/term_.rs @@ -0,0 +1,34 @@ +use nu_engine::{command_prelude::*, get_full_help}; + +#[derive(Clone)] +pub struct Term; + +impl Command for Term { + fn name(&self) -> &str { + "term" + } + + fn signature(&self) -> Signature { + Signature::build("term") + .category(Category::Platform) + .input_output_types(vec![(Type::Nothing, Type::String)]) + } + + fn description(&self) -> &str { + "Commands for querying information about the terminal." + } + + fn extra_description(&self) -> &str { + "You must use one of the following subcommands. Using this command as-is will only produce this help message." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/platform/term/term_query.rs b/crates/nu-command/src/platform/term/term_query.rs new file mode 100644 index 0000000000..a4966edde0 --- /dev/null +++ b/crates/nu-command/src/platform/term/term_query.rs @@ -0,0 +1,137 @@ +use std::{ + io::{Read, Write}, + time::Duration, +}; + +use nu_engine::command_prelude::*; + +const CTRL_C: u8 = 3; + +#[derive(Clone)] +pub struct TermQuery; + +impl Command for TermQuery { + fn name(&self) -> &str { + "term query" + } + + fn description(&self) -> &str { + "Query the terminal for information." + } + + fn extra_description(&self) -> &str { + "Print the given query, and read the immediate result from stdin. + +The standard input will be read right after `query` is printed, and consumed until the `terminator` +sequence is encountered. The `terminator` is not removed from the output. + +If `terminator` is not supplied, input will be read until Ctrl-C is pressed." + } + + fn signature(&self) -> Signature { + Signature::build("term query") + .category(Category::Platform) + .input_output_types(vec![(Type::Nothing, Type::Binary)]) + .allow_variants_without_examples(true) + .required( + "query", + SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), + "The query that will be printed to stdout.", + ) + .named( + "terminator", + SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), + "Terminator sequence for the expected reply.", + Some('t'), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get cursor position.", + example: r#"term query (ansi cursor_position) --terminator 'R'"#, + result: None, + }, + Example { + description: "Get terminal background color.", + example: r#"term query $'(ansi osc)10;?(ansi st)' --terminator (ansi st)"#, + result: None, + }, + Example { + description: "Read clipboard content on terminals supporting OSC-52.", + example: r#"term query $'(ansi osc)52;c;?(ansi st)' --terminator (ansi st)"#, + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let query: Vec = call.req(engine_state, stack, 0)?; + let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; + + crossterm::terminal::enable_raw_mode()?; + + // clear terminal events + while crossterm::event::poll(Duration::from_secs(0))? { + // If there's an event, read it to remove it from the queue + let _ = crossterm::event::read()?; + } + + let mut b = [0u8; 1]; + let mut buf = vec![]; + let mut stdin = std::io::stdin().lock(); + + { + let mut stdout = std::io::stdout().lock(); + stdout.write_all(&query)?; + stdout.flush()?; + } + + let out = if let Some(terminator) = terminator { + loop { + if let Err(err) = stdin.read_exact(&mut b) { + break Err(ShellError::from(err)); + } + + if b[0] == CTRL_C { + break Err(ShellError::Interrupted { span: call.head }); + } + + buf.push(b[0]); + + if buf.ends_with(&terminator) { + break Ok(Value::Binary { + val: buf, + internal_span: call.head, + } + .into_pipeline_data()); + } + } + } else { + loop { + if let Err(err) = stdin.read_exact(&mut b) { + break Err(ShellError::from(err)); + } + + if b[0] == CTRL_C { + break Ok(Value::Binary { + val: buf, + internal_span: call.head, + } + .into_pipeline_data()); + } + + buf.push(b[0]); + } + }; + crossterm::terminal::disable_raw_mode()?; + out + } +} diff --git a/crates/nu-command/src/platform/term_size.rs b/crates/nu-command/src/platform/term/term_size.rs similarity index 100% rename from crates/nu-command/src/platform/term_size.rs rename to crates/nu-command/src/platform/term/term_size.rs From 7a9b14b49dda6ecfa0ccd8b165328c4993e53d66 Mon Sep 17 00:00:00 2001 From: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:25:43 -0500 Subject: [PATCH 07/99] Add example for PROMPT_COMMAND_RIGHT (#14439) # Description I just completely left out `$env.PROMPT_COMMAND_RIGHT` in the `sample_env.nu`. This adds it in. # User-Facing Changes `config env --sample` will now include doc for `PROMPT_COMMAND_RIGHT`. # Tests + Formatting Doc-only # After Submitting n/a --- crates/nu-utils/src/default_files/sample_env.nu | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/nu-utils/src/default_files/sample_env.nu b/crates/nu-utils/src/default_files/sample_env.nu index 84926abb2f..23dc925ccd 100644 --- a/crates/nu-utils/src/default_files/sample_env.nu +++ b/crates/nu-utils/src/default_files/sample_env.nu @@ -24,6 +24,11 @@ $env.PROMPT_COMMAND = "Nushell" # Simple example - Dynamic closure displaying the path: $env.PROMPT_COMMAND = {|| pwd} +# PROMPT_COMMAND_RIGHT +# -------------------- +# Defines a prompt which will appear right-aligned in the terminal +$env.PROMPT_COMMAND_RIGHT = {|| date now | format date "%d-%a %r" } + # PROMPT_INDICATOR* # ----------------- # The prompt indicators are environmental variables that represent From ac75562296b1f3bff3d3d49b79ac2b5f73f4f956 Mon Sep 17 00:00:00 2001 From: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:22:18 -0500 Subject: [PATCH 08/99] Remove long-unused `autoenv` tests (#14436) # Description The `.nu-env` file feature was removed some time ago (probably in the engine-q upgrade?). The tests, however, still remained as dead-code, so this is just some basic clean-up. If this feature was ever implemented again, the tests would need to be rewritten anyway due to the changes in the way config is handled. # User-Facing Changes None # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` - # After Submitting N/A --- tests/shell/environment/env.rs | 26 -- tests/shell/environment/mod.rs | 21 - tests/shell/environment/nu_env.rs | 668 ------------------------------ 3 files changed, 715 deletions(-) delete mode 100644 tests/shell/environment/nu_env.rs diff --git a/tests/shell/environment/env.rs b/tests/shell/environment/env.rs index 35692fdee9..34910fd98e 100644 --- a/tests/shell/environment/env.rs +++ b/tests/shell/environment/env.rs @@ -1,10 +1,7 @@ -use super::support::Trusted; - use nu_test_support::fs::Stub::FileWithContent; use nu_test_support::playground::Playground; use nu_test_support::{nu, nu_repl_code, nu_with_std}; use pretty_assertions::assert_eq; -use serial_test::serial; #[test] fn env_shorthand() { @@ -157,29 +154,6 @@ fn has_file_loc() { }) } -// FIXME: autoenv not currently implemented -#[ignore] -#[test] -#[serial] -fn passes_env_from_local_cfg_to_external_process() { - Playground::setup("autoenv_dir", |dirs, sandbox| { - sandbox.with_files(&[FileWithContent( - ".nu-env", - r#"[env] - FOO = "foo" - "#, - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), " - nu --testbin echo_env FOO - ") - }); - - assert_eq!(actual.out, "foo"); - }) -} - #[test] fn hides_env_in_block() { let inp = &[ diff --git a/tests/shell/environment/mod.rs b/tests/shell/environment/mod.rs index faf884f166..b94b8e4346 100644 --- a/tests/shell/environment/mod.rs +++ b/tests/shell/environment/mod.rs @@ -1,22 +1 @@ mod env; - -// FIXME: nu_env tests depend on autoenv which hasn't been ported yet -// mod nu_env; - -pub mod support { - use nu_test_support::{nu, playground::*, Outcome}; - - pub struct Trusted; - - impl Trusted { - pub fn in_path(dirs: &Dirs, block: impl FnOnce() -> Outcome) -> Outcome { - let for_env_manifest = dirs.test().to_string_lossy(); - - nu!(cwd: dirs.root(), "autoenv trust \"{}\"", for_env_manifest); - let out = block(); - nu!(cwd: dirs.root(), "autoenv untrust \"{}\"", for_env_manifest); - - out - } - } -} diff --git a/tests/shell/environment/nu_env.rs b/tests/shell/environment/nu_env.rs deleted file mode 100644 index d249d90678..0000000000 --- a/tests/shell/environment/nu_env.rs +++ /dev/null @@ -1,668 +0,0 @@ -use super::support::Trusted; - -use nu_test_support::fs::Stub::FileWithContent; -use nu_test_support::playground::Playground; -use nu_test_support::{nu, pipeline}; -use pretty_assertions::assert_eq; - -use serial_test::serial; - -const SCRIPTS: &str = r#"startup = ["touch hello.txt"] - on_exit = ["touch bye.txt"]"#; - -#[test] -#[serial] -fn picks_up_env_keys_when_entering_trusted_directory() { - Playground::setup("autoenv_test_1", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let expected = "testvalue"; - - let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.testkey")); - - assert_eq!(actual.out, expected); - }) -} - -#[test] -#[serial] -fn picks_up_and_lets_go_env_keys_when_entering_trusted_directory_with_implied_cd() { - use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("autoenv_test", |dirs, sandbox| { - sandbox.mkdir("foo"); - sandbox.mkdir("foo/bar"); - sandbox.with_files(vec![ - FileWithContent( - "foo/.nu-env", - r#"[env] - testkey = "testvalue" - "#, - ), - FileWithContent( - "foo/bar/.nu-env", - r#" - [env] - bar = "true" - "#, - ), - ]); - let actual = nu!( - cwd: dirs.test(), - " - do {autoenv trust -q foo ; = null } - foo - echo $env.testkey" - ); - assert_eq!(actual.out, "testvalue"); - //Assert testkey is gone when leaving foo - let actual = nu!( - cwd: dirs.test(), - " - do {autoenv trust -q foo; = null } ; - foo - .. - echo $env.testkey - " - ); - assert!(actual.err.contains("Unknown")); - //Assert testkey is present also when jumping over foo - let actual = nu!( - cwd: dirs.test(), - " - do {autoenv trust -q foo; = null } ; - do {autoenv trust -q foo/bar; = null } ; - foo/bar - echo $env.testkey - echo $env.bar - " - ); - assert_eq!(actual.out, "testvaluetrue"); - //Assert bar removed after leaving bar - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; - foo/bar - ../.. - echo $env.bar" - ); - assert!(actual.err.contains("Unknown")); - }); -} - -#[test] -#[serial] -#[ignore] -fn picks_up_script_vars_when_entering_trusted_directory() { - Playground::setup("autoenv_test_2", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let expected = "myval"; - - let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.myscript")); - - // scriptvars are not supported - // and why is myval expected when myscript is "echo myval" - assert_eq!(actual.out, expected); - }) -} - -#[test] -#[serial] -fn picks_up_env_keys_when_entering_trusted_directory_indirectly() { - Playground::setup("autoenv_test_3", |dirs, sandbox| { - sandbox.mkdir("crates"); - sandbox.with_files(vec![FileWithContent( - ".nu-env", - r#"[env] - nu-ver = "0.30.0" "#, - )]); - - let expected = "0.30.0"; - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test().join("crates"), " - cd ../../autoenv_test_3 - echo $env.nu-ver - ") - }); - - assert_eq!(actual.out, expected); - }) -} - -#[test] -#[serial] -fn entering_a_trusted_directory_runs_entry_scripts() { - Playground::setup("autoenv_test_4", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - "# - ), - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), pipeline(r#" - ls - | where name == "hello.txt" - | get name - "#)) - }); - - assert_eq!(actual.out, "hello.txt"); - }) -} - -#[test] -#[serial] -fn leaving_a_trusted_directory_runs_exit_scripts() { - Playground::setup("autoenv_test_5", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), r#" - cd .. - ls autoenv_test_5 | get name | path basename | where $it == "bye.txt" - "#) - }); - - assert_eq!(actual.out, "bye.txt"); - }) -} - -#[test] -#[serial] -fn entry_scripts_are_called_when_revisiting_a_trusted_directory() { - Playground::setup("autoenv_test_6", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), r#" - do { rm hello.txt | ignore } ; # Silence file deletion message from output - cd .. - cd autoenv_test_6 - ls | where name == "hello.txt" | get name - "#) - }); - - assert_eq!(actual.out, "hello.txt"); - }) -} - -#[test] -#[serial] -fn given_a_trusted_directory_with_entry_scripts_when_entering_a_subdirectory_entry_scripts_are_not_called( -) { - Playground::setup("autoenv_test_7", |dirs, sandbox| { - sandbox.mkdir("time_to_cook_arepas"); - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), r#" - cd time_to_cook_arepas - ls | where name == "hello.txt" | length - "#) - }); - - assert_eq!(actual.out, "0"); - }) -} - -#[test] -#[serial] -fn given_a_trusted_directory_with_exit_scripts_when_entering_a_subdirectory_exit_scripts_are_not_called( -) { - Playground::setup("autoenv_test_8", |dirs, sandbox| { - sandbox.mkdir("time_to_cook_arepas"); - sandbox.with_files(vec![FileWithContent( - ".nu-env", - &format!( - "{}\n{}", - SCRIPTS, - r#"[env] - testkey = "testvalue" - - [scriptvars] - myscript = "echo myval" - "# - ), - )]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), r#" - cd time_to_cook_arepas - ls | where name == "bye.txt" | length - "#) - }); - - assert_eq!(actual.out, "0"); - }) -} - -#[test] -#[serial] -fn given_a_hierarchy_of_trusted_directories_when_entering_in_any_nested_ones_should_carry_over_variables_set_from_the_root( -) { - Playground::setup("autoenv_test_9", |dirs, sandbox| { - sandbox.mkdir("nu_plugin_rb"); - sandbox.with_files(vec![ - FileWithContent( - ".nu-env", - r#"[env] - organization = "nushell""#, - ), - FileWithContent( - "nu_plugin_rb/.nu-env", - r#"[env] - language = "Ruby""#, - ), - ]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test().parent().unwrap(), " - do { autoenv trust -q autoenv_test_9/nu_plugin_rb ; = null } # Silence autoenv trust -q message from output - cd autoenv_test_9/nu_plugin_rb - echo $env.organization - ") - }); - - assert_eq!(actual.out, "nushell"); - }) -} - -#[test] -#[serial] -fn given_a_hierarchy_of_trusted_directories_nested_ones_should_overwrite_variables_from_parent_directories( -) { - Playground::setup("autoenv_test_10", |dirs, sandbox| { - sandbox.mkdir("nu_plugin_rb"); - sandbox.with_files(vec![ - FileWithContent( - ".nu-env", - r#"[env] - organization = "nushell""#, - ), - FileWithContent( - "nu_plugin_rb/.nu-env", - r#"[env] - organization = "Andrab""#, - ), - ]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test().parent().unwrap(), " - do { autoenv trust -q autoenv_test_10/nu_plugin_rb ; = null } # Silence autoenv trust -q message from output - cd autoenv_test_10/nu_plugin_rb - echo $env.organization - ") - }); - - assert_eq!(actual.out, "Andrab"); - }) -} - -#[test] -#[serial] -#[cfg(not(windows))] //TODO figure out why this test doesn't work on windows -fn local_config_should_not_be_added_when_running_scripts() { - Playground::setup("autoenv_test_10", |dirs, sandbox| { - sandbox.mkdir("foo"); - sandbox.with_files(vec![ - FileWithContent( - ".nu-env", - r#"[env] - organization = "nu""#, - ), - FileWithContent( - "foo/.nu-env", - r#"[env] - organization = "foo""#, - ), - FileWithContent( - "script.nu", - "cd foo - echo $env.organization", - ), - ]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test(), " - do { autoenv trust -q foo } # Silence autoenv trust message from output - nu script.nu - ") - }); - - assert_eq!(actual.out, "nu"); - }) -} -#[test] -#[serial] -fn given_a_hierarchy_of_trusted_directories_going_back_restores_overwritten_variables() { - Playground::setup("autoenv_test_11", |dirs, sandbox| { - sandbox.mkdir("nu_plugin_rb"); - sandbox.with_files(vec![ - FileWithContent( - ".nu-env", - r#"[env] - organization = "nushell""#, - ), - FileWithContent( - "nu_plugin_rb/.nu-env", - r#"[env] - organization = "Andrab""#, - ), - ]); - - let actual = Trusted::in_path(&dirs, || { - nu!(cwd: dirs.test().parent().unwrap(), " - do { autoenv trust -q autoenv_test_11/nu_plugin_rb } # Silence autoenv trust message from output - cd autoenv_test_11 - cd nu_plugin_rb - do { rm ../.nu-env | ignore } # By deleting the root nu-env we have guarantees that the variable gets restored (not by autoenv when re-entering) - cd .. - echo $env.organization - ") - }); - - assert_eq!(actual.out, "nushell"); - }) -} - -#[test] -#[serial] -fn local_config_env_var_present_and_removed_correctly() { - use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("autoenv_test", |dirs, sandbox| { - sandbox.mkdir("foo"); - sandbox.mkdir("foo/bar"); - sandbox.with_files(vec![FileWithContent( - "foo/.nu-env", - r#"[env] - testkey = "testvalue" - "#, - )]); - //Assert testkey is not present before entering directory - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; - echo $env.testkey" - ); - assert!(actual.err.contains("Unknown")); - //Assert testkey is present in foo - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; cd foo - echo $env.testkey" - ); - assert_eq!(actual.out, "testvalue"); - //Assert testkey is present also in subdirectories - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; cd foo - cd bar - echo $env.testkey" - ); - assert_eq!(actual.out, "testvalue"); - //Assert testkey is present also when jumping over foo - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; cd foo/bar - echo $env.testkey" - ); - assert_eq!(actual.out, "testvalue"); - //Assert testkey removed after leaving foo - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; cd foo - cd .. - echo $env.testkey" - ); - assert!(actual.err.contains("Unknown")); - }); -} - -#[test] -#[serial] -fn local_config_env_var_gets_overwritten() { - use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("autoenv_test", |dirs, sandbox| { - sandbox.mkdir("foo"); - sandbox.mkdir("foo/bar"); - sandbox.with_files(vec![ - FileWithContent( - "foo/.nu-env", - r#"[env] - overwrite_me = "foo" - "#, - ), - FileWithContent( - "foo/bar/.nu-env", - r#"[env] - overwrite_me = "bar" - "#, - ), - ]); - //Assert overwrite_me is not present before entering directory - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; - echo $env.overwrite_me" - ); - assert!(actual.err.contains("Unknown")); - //Assert overwrite_me is foo in foo - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; cd foo - echo $env.overwrite_me" - ); - assert_eq!(actual.out, "foo"); - //Assert overwrite_me is bar in bar - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo - autoenv trust -q foo/bar - cd foo - cd bar - echo $env.overwrite_me" - ); - assert_eq!(actual.out, "bar"); - //Assert overwrite_me is present also when jumping over foo - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; autoenv trust -q foo/bar; cd foo/bar - echo $env.overwrite_me - " - ); - assert_eq!(actual.out, "bar"); - //Assert overwrite_me removed after leaving bar - let actual = nu!( - cwd: dirs.test(), - "autoenv trust -q foo; autoenv trust -q foo/bar; cd foo - cd bar - cd .. - echo $env.overwrite_me" - ); - assert_eq!(actual.out, "foo"); - }); -} - -#[test] -#[serial] -fn autoenv_test_entry_scripts() { - use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("autoenv_test", |dirs, sandbox| { - sandbox.mkdir("foo/bar"); - - // Windows uses a different command to create an empty file so we need to have different content on windows. - let nu_env = if cfg!(target_os = "windows") { - r#"startup = ["echo nul > hello.txt"]"# - } else { - r#"startup = ["touch hello.txt"]"# - }; - - sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); - - // Make sure entryscript is run when entering directory - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo - ls | where name == "hello.txt" | get name"# - ); - assert!(actual.out.contains("hello.txt")); - - // Make sure entry scripts are also run when jumping over directory - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo/bar - ls .. | where name == "../hello.txt" | get name"# - ); - assert!(actual.out.contains("hello.txt")); - - // Entryscripts should not run after changing to a subdirectory. - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo - rm hello.txt - cd bar - ls .. | where name == "../hello.txt" | length"# - ); - assert!(actual.out.contains('0')); - }); -} - -#[test] -#[serial] -fn autoenv_test_exit_scripts() { - use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("autoenv_test", |dirs, sandbox| { - sandbox.mkdir("foo/bar"); - - // Windows uses a different command to create an empty file so we need to have different content on windows. - let nu_env = r#"on_exit = ["touch bye.txt"]"#; - - sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); - - // Make sure exitscript is run - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo - cd .. - ls foo | where name =~ "bye.txt" | length - rm foo/bye.txt | ignore; cd . - "# - ); - assert_eq!(actual.out, "1"); - - // Entering a subdir should not trigger exitscripts - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo - cd bar - ls .. | where name =~ "bye.txt" | length"# - ); - assert_eq!(actual.out, "0"); - - // Also run exitscripts when jumping over directory - let actual = nu!( - cwd: dirs.test(), - r#"autoenv trust -q foo - cd foo/bar - cd ../.. - ls foo | where name =~ "bye.txt" | length - rm foo/bye.txt | ignore; cd ."# - ); - assert_eq!(actual.out, "1"); - }); -} - -#[test] -#[serial] -#[cfg(unix)] -fn prepends_path_from_local_config() { - //If this test fails for you, make sure that your environment from which you start nu - //contains some env vars - Playground::setup("autoenv_test_1", |dirs, sandbox| { - sandbox.with_files(vec![FileWithContent( - ".nu-env", - r#" - path = ["/hi", "/nushell"] - "#, - )]); - - let expected = "[\"/hi\",\"/nushell\","; - - let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $nu.path | to json")); - // assert_eq!("", actual.out); - assert!(actual.out.starts_with(expected)); - assert!(actual.out.len() > expected.len()); - }) -} From 367fb9b5049ec33ff8ef5f856557e9a5d2603b62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:27:47 +0800 Subject: [PATCH 09/99] Bump crate-ci/typos from 1.27.3 to 1.28.1 (#14447) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.27.3 to 1.28.1.
Release notes

Sourced from crate-ci/typos's releases.

v1.28.1

[1.28.1] - 2024-11-26

Fixes

  • Add back in lock file types accidentally removed in 1.28 (go.sum, requirements.txt)

v1.28.0

[1.28.0] - 2024-11-25

Features

  • Updated the dictionary with the November 2024 changes
  • Add many new types and file extensions to the --type-list, including ada, alire, bat, candid, carp, cml, devicetree, dita, dockercompose, grpbuild, graphql, hare, lean, meson, prolog, raku, reasonml, rescript, solidity, svelte, usd, v, wgsl
Changelog

Sourced from crate-ci/typos's changelog.

[1.28.1] - 2024-11-26

Fixes

  • Add back in lock file types accidentally removed in 1.28 (go.sum, requirements.txt)

[1.28.0] - 2024-11-25

Features

  • Updated the dictionary with the November 2024 changes
  • Add many new types and file extensions to the --type-list, including ada, alire, bat, candid, carp, cml, devicetree, dita, dockercompose, grpbuild, graphql, hare, lean, meson, prolog, raku, reasonml, rescript, solidity, svelte, usd, v, wgsl
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.27.3&new-version=1.28.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 2c0fcbd771..d21926517e 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.27.3 + uses: crate-ci/typos@v1.28.1 From 186c08467f84f3082b1460483400b32015a8925a Mon Sep 17 00:00:00 2001 From: Wind Date: Wed, 27 Nov 2024 09:29:25 +0800 Subject: [PATCH 10/99] make std help more user friendly (#14347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #13159 After the change, `std help` will no-longer print out "double error" messages. Actually I think it's tricky to make it right. To make `help ` keeps paging feature from fallback `man` command. I have to split `commands` into `scope-commands` and `external-commands`. If we don't split it, simply call `let commands = (try { commands $target_item --find $find })` in `help main` will cause us to lost paging feature, which is not we want. A comment from original issue: > If there are no objections, I'd like to remove the man page fallback code from std help for the moment. While it's probably fixable, it's also platform specific and requires testing on all platforms. It also seems like a low-value add here. Actually I think it's a beautiful feature of `std help`, so I want to keep it here. # User-Facing Changes ### Before ```nushell > help commands asdfadsf Help pages from external command asdfadsf: No manual entry for asdfadsf Error: × std::help::command_not_found ╭─[entry #11:1:15] 1 │ help commands asdfadsf · ────┬─── · ╰── command not found ╰──── ``` ### After ```nushell > help commands asdfasdf Help pages from external command asdfasdf: No manual entry for asdfasdf ``` # Tests + Formatting Actually it's a little hard to add test because it required user input (especially for fallback `man` command) --- crates/nu-std/std/help/mod.nu | 48 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/crates/nu-std/std/help/mod.nu b/crates/nu-std/std/help/mod.nu index 9f13f1bb18..85edd3790d 100644 --- a/crates/nu-std/std/help/mod.nu +++ b/crates/nu-std/std/help/mod.nu @@ -684,8 +684,7 @@ def build-command-page [command: record] { ] | flatten | str join "\n" } -# Show help on commands. -export def commands [ +def scope-commands [ ...command: string@"nu-complete list-commands" # the name of command to get help on --find (-f): string # string to find in command names and description ] { @@ -699,20 +698,35 @@ export def commands [ let found_command = ($commands | where name == $target_command) if ($found_command | is-empty) { - try { - print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)" - ^($env.NU_HELPER? | default "man") $target_command - } catch { - command-not-found-error (metadata $command | get span) - } + command-not-found-error (metadata $command | get span) + } else { + build-command-page ($found_command | get 0) } - - build-command-page ($found_command | get 0) } else { $commands | select name category description signatures search_terms } } +def external-commands [ + ...command: string@"nu-complete list-commands", +] { + let target_command = $command | str join " " + print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)" + ^($env.NU_HELPER? | default "man") $target_command +} + +# Show help on commands. +export def commands [ + ...command: string@"nu-complete list-commands" # the name of command to get help on + --find (-f): string # string to find in command names and description +] { + try { + scope-commands ...$command --find=$find + } catch { + external-commands ...$command + } +} + def pretty-cmd [] { let cmd = $in $"(ansi default_dimmed)(ansi default_italic)($cmd)(ansi reset)" @@ -763,7 +777,7 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https let target_item = ($item | str join " ") - let commands = (try { commands $target_item --find $find }) + let commands = (try { scope-commands $target_item --find $find }) if not ($commands | is-empty) { return $commands } let aliases = (try { aliases $target_item --find $find }) @@ -776,13 +790,7 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https print -e $"No help results found mentioning: ($find)" return [] } - - let span = (metadata $item | get span) - error make { - msg: ("std::help::item_not_found" | error-fmt) - label: { - text: "item not found" - span: $span - } - } + # use external tool (e.g: `man`) to search help for $target_item + # the stdout and stderr of external tool will follow `main` call. + external-commands $target_item } From 4edce44689978ddbfc88841b216c4092922a54b1 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 26 Nov 2024 17:35:55 -0800 Subject: [PATCH 11/99] Remove `ListStream` type (#14425) # Description List values and list streams have the same type (`list<>`). Rather, streaming is a separate property of the pipeline/command output. This PR removes the unnecessary `ListStream` type. # User-Facing Changes Should be none, except `random dice` now has a more specific output type. --- crates/nu-command/src/database/commands/into_sqlite.rs | 1 - crates/nu-command/src/random/dice.rs | 2 +- crates/nu-command/src/system/nu_check.rs | 1 - crates/nu-parser/src/type_check.rs | 2 -- crates/nu-protocol/src/pipeline/pipeline_data.rs | 4 ++-- crates/nu-protocol/src/ty.rs | 4 ---- crates/nu_plugin_example/src/commands/for_each.rs | 2 +- crates/nu_plugin_example/src/commands/generate.rs | 2 +- 8 files changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/nu-command/src/database/commands/into_sqlite.rs b/crates/nu-command/src/database/commands/into_sqlite.rs index fcfa228f3b..3236f90999 100644 --- a/crates/nu-command/src/database/commands/into_sqlite.rs +++ b/crates/nu-command/src/database/commands/into_sqlite.rs @@ -359,7 +359,6 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> { | Type::Custom(_) | Type::Error | Type::List(_) - | Type::ListStream | Type::Range | Type::Record(_) | Type::Signature diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index b11eb59e3e..32768a9937 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -12,7 +12,7 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("random dice") - .input_output_types(vec![(Type::Nothing, Type::ListStream)]) + .input_output_types(vec![(Type::Nothing, Type::list(Type::Int))]) .allow_variants_without_examples(true) .named( "dice", diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 59982dfaa7..945440cca4 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -15,7 +15,6 @@ impl Command for NuCheck { Signature::build("nu-check") .input_output_types(vec![ (Type::String, Type::Bool), - (Type::ListStream, Type::Bool), (Type::List(Box::new(Type::Any)), Type::Bool), ]) // type is string to avoid automatically canonicalizing the path diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index e5f2a22921..fd57acd5ca 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -29,8 +29,6 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { match (lhs, rhs) { (Type::List(c), Type::List(d)) => type_compatible(c, d), - (Type::ListStream, Type::List(_)) => true, - (Type::List(_), Type::ListStream) => true, (Type::List(c), Type::Table(table_fields)) => { if matches!(**c, Type::Any) { return true; diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index 75449f547f..af56533e91 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -109,7 +109,7 @@ impl PipelineData { /// than would be returned by [`Value::get_type()`] on the result of /// [`.into_value()`](Self::into_value). /// - /// Specifically, a `ListStream` results in [`list stream`](Type::ListStream) rather than + /// Specifically, a `ListStream` results in `list` rather than /// the fully complete [`list`](Type::List) type (which would require knowing the contents), /// and a `ByteStream` with [unknown](crate::ByteStreamType::Unknown) type results in /// [`any`](Type::Any) rather than [`string`](Type::String) or [`binary`](Type::Binary). @@ -117,7 +117,7 @@ impl PipelineData { match self { PipelineData::Empty => Type::Nothing, PipelineData::Value(value, _) => value.get_type(), - PipelineData::ListStream(_, _) => Type::ListStream, + PipelineData::ListStream(_, _) => Type::list(Type::Any), PipelineData::ByteStream(stream, _) => stream.type_().into(), } } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 55da6422b9..d1b6f7140a 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -23,7 +23,6 @@ pub enum Type { Float, Int, List(Box), - ListStream, #[default] Nothing, Number, @@ -121,7 +120,6 @@ impl Type { Type::Nothing => SyntaxShape::Nothing, Type::Record(entries) => SyntaxShape::Record(mk_shape(entries)), Type::Table(columns) => SyntaxShape::Table(mk_shape(columns)), - Type::ListStream => SyntaxShape::List(Box::new(SyntaxShape::Any)), Type::Any => SyntaxShape::Any, Type::Error => SyntaxShape::Any, Type::Binary => SyntaxShape::Binary, @@ -151,7 +149,6 @@ impl Type { Type::Nothing => String::from("nothing"), Type::Number => String::from("number"), Type::String => String::from("string"), - Type::ListStream => String::from("list-stream"), Type::Any => String::from("any"), Type::Error => String::from("error"), Type::Binary => String::from("binary"), @@ -209,7 +206,6 @@ impl Display for Type { Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), Type::String => write!(f, "string"), - Type::ListStream => write!(f, "list-stream"), Type::Any => write!(f, "any"), Type::Error => write!(f, "error"), Type::Binary => write!(f, "binary"), diff --git a/crates/nu_plugin_example/src/commands/for_each.rs b/crates/nu_plugin_example/src/commands/for_each.rs index e66a3841dc..20bf7e9f12 100644 --- a/crates/nu_plugin_example/src/commands/for_each.rs +++ b/crates/nu_plugin_example/src/commands/for_each.rs @@ -23,7 +23,7 @@ impl PluginCommand for ForEach { fn signature(&self) -> Signature { Signature::build(self.name()) - .input_output_type(Type::ListStream, Type::Nothing) + .input_output_type(Type::list(Type::Any), Type::Nothing) .required( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), diff --git a/crates/nu_plugin_example/src/commands/generate.rs b/crates/nu_plugin_example/src/commands/generate.rs index de63849aef..d2d4e7a5af 100644 --- a/crates/nu_plugin_example/src/commands/generate.rs +++ b/crates/nu_plugin_example/src/commands/generate.rs @@ -26,7 +26,7 @@ impl PluginCommand for Generate { fn signature(&self) -> Signature { Signature::build(self.name()) - .input_output_type(Type::Nothing, Type::ListStream) + .input_output_type(Type::Nothing, Type::list(Type::Any)) .required( "initial", SyntaxShape::Any, From e0c0d39edecad231ad3823d38fb9d5505d693d85 Mon Sep 17 00:00:00 2001 From: Wind Date: Wed, 27 Nov 2024 09:36:30 +0800 Subject: [PATCH 12/99] deprecate --ignore-shell-errors and --ignore-program-errors in `do` (#14385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description As title, this pr is going to deprecate `--ignore-shell-errors` and `--ignore-program-errors`. Because I think these two flags makes `do` command complicate, and it should be easy to use `-i` instead. # User-Facing Changes After the pr, using these two flags will raise deprecated warning. ```nushell > do --ignore-program-errors { ^pwd } Error: × Deprecated option ╭─[entry #2:1:1] 1 │ do --ignore-program-errors { ^pwd } · ─┬ · ╰── `--ignore-program-errors` is deprecated and will be removed in 0.102.0. ╰──── help: Please use the `--ignore-errors(-i)` /home/windsoilder/projects/nushell > do --ignore-shell-errors { ^pwd } Error: × Deprecated option ╭─[entry #3:1:1] 1 │ do --ignore-shell-errors { ^pwd } · ─┬ · ╰── `--ignore-shell-errors` is deprecated and will be removed in 0.102.0. ╰──── help: Please use the `--ignore-errors(-i)` /home/windsoilder/projects/nushell ``` # Tests + Formatting NaN --- crates/nu-cmd-lang/src/core_commands/do_.rs | 37 ++++++++++++++----- crates/nu-command/tests/commands/do_.rs | 7 ++-- .../nu-utils/src/default_files/default_env.nu | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 5f1b42cb66..81ea22ad39 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -69,6 +69,33 @@ impl Command for Do { let block: Closure = call.req(engine_state, caller_stack, 0)?; let rest: Vec = call.rest(engine_state, caller_stack, 1)?; let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?; + + if call.has_flag(engine_state, caller_stack, "ignore-shell-errors")? { + nu_protocol::report_shell_warning( + engine_state, + &ShellError::GenericError { + error: "Deprecated option".into(), + msg: "`--ignore-shell-errors` is deprecated and will be removed in 0.102.0." + .into(), + span: Some(call.head), + help: Some("Please use the `--ignore-errors(-i)`".into()), + inner: vec![], + }, + ); + } + if call.has_flag(engine_state, caller_stack, "ignore-program-errors")? { + nu_protocol::report_shell_warning( + engine_state, + &ShellError::GenericError { + error: "Deprecated option".into(), + msg: "`--ignore-program-errors` is deprecated and will be removed in 0.102.0." + .into(), + span: Some(call.head), + help: Some("Please use the `--ignore-errors(-i)`".into()), + inner: vec![], + }, + ); + } let ignore_shell_errors = ignore_all_errors || call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?; let ignore_program_errors = ignore_all_errors @@ -208,16 +235,6 @@ impl Command for Do { example: r#"do --ignore-errors { thisisnotarealcommand }"#, result: None, }, - Example { - description: "Run the closure and ignore shell errors", - example: r#"do --ignore-shell-errors { thisisnotarealcommand }"#, - result: None, - }, - Example { - description: "Run the closure and ignore external program errors", - example: r#"do --ignore-program-errors { nu --commands 'exit 1' }; echo "I'll still run""#, - result: None, - }, Example { description: "Abort the pipeline if a program returns a non-zero exit code", example: r#"do --capture-errors { nu --commands 'exit 1' } | myscarycommand"#, diff --git a/crates/nu-command/tests/commands/do_.rs b/crates/nu-command/tests/commands/do_.rs index bbce5648db..1a2258cf9c 100644 --- a/crates/nu-command/tests/commands/do_.rs +++ b/crates/nu-command/tests/commands/do_.rs @@ -44,7 +44,7 @@ fn do_with_semicolon_break_on_failed_external() { fn ignore_shell_errors_works_for_external_with_semicolon() { let actual = nu!(r#"do -s { open asdfasdf.txt }; "text""#); - assert_eq!(actual.err, ""); + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "text"); } @@ -52,7 +52,7 @@ fn ignore_shell_errors_works_for_external_with_semicolon() { fn ignore_program_errors_works_for_external_with_semicolon() { let actual = nu!(r#"do -p { nu -n -c 'exit 1' }; "text""#); - assert_eq!(actual.err, ""); + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "text"); } @@ -80,6 +80,7 @@ fn run_closure_with_it_using() { #[test] fn waits_for_external() { let actual = nu!(r#"do -p { nu -c 'sleep 1sec; print before; exit 1'}; print after"#); - assert!(actual.err.is_empty()); + + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "beforeafter"); } diff --git a/crates/nu-utils/src/default_files/default_env.nu b/crates/nu-utils/src/default_files/default_env.nu index 498c9670b3..1f702d984b 100644 --- a/crates/nu-utils/src/default_files/default_env.nu +++ b/crates/nu-utils/src/default_files/default_env.nu @@ -4,7 +4,7 @@ # version = "0.100.1" $env.PROMPT_COMMAND = {|| - let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { + let dir = match (do -i { $env.PWD | path relative-to $nu.home-path }) { null => $env.PWD '' => '~' $relative_pwd => ([~ $relative_pwd] | path join) From 547c436281fff93c54feb612cd17fe88678626fa Mon Sep 17 00:00:00 2001 From: Antoine Stevan <44101798+amtoine@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:43:49 +0100 Subject: [PATCH 13/99] add `from ndnuon` and `to ndnuon` to stdlib (#14334) # Description i was playing with the NDNUON format and using local definitions of `from ndnuon` and `to ndnuon` but then i thought they could live in the standard library next to `from ndjson` and `to ndjson` :yum: # User-Facing Changes users can now add the following to their configs and get NDNUON ready to go ```nushell use std formats ["from ndnuon" "to ndnuon"] ``` # Tests + Formatting i did simply mimic the tests for `from ndjson` and `to ndjson`, i hope it's fine since the recent big change to the standard library # After Submitting --------- Co-authored-by: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> --- crates/nu-std/std/formats/mod.nu | 10 ++++ crates/nu-std/tests/test_formats.nu | 62 +++++++++++++++++++++---- crates/nu-std/tests/test_std_formats.nu | 62 +++++++++++++++++++++---- 3 files changed, 116 insertions(+), 18 deletions(-) diff --git a/crates/nu-std/std/formats/mod.nu b/crates/nu-std/std/formats/mod.nu index 2f20e81451..addfa4c1c9 100644 --- a/crates/nu-std/std/formats/mod.nu +++ b/crates/nu-std/std/formats/mod.nu @@ -28,3 +28,13 @@ export def "to ndjson" []: any -> string { export def "to jsonl" []: any -> string { each { to json --raw } | to text } + +# Convert from NDNUON (newline-delimited NUON), to structured data +export def "from ndnuon" []: [string -> any] { + lines | each { from nuon } +} + +# Convert structured data to NDNUON, i.e. newline-delimited NUON +export def "to ndnuon" []: [any -> string] { + each { to nuon --raw } | to text +} diff --git a/crates/nu-std/tests/test_formats.nu b/crates/nu-std/tests/test_formats.nu index f54598bf08..ec1f6adb47 100644 --- a/crates/nu-std/tests/test_formats.nu +++ b/crates/nu-std/tests/test_formats.nu @@ -2,15 +2,26 @@ use std/assert use std/formats * -def test_data_multiline [] { - let lines = [ - "{\"a\":1}", - "{\"a\":2}", - "{\"a\":3}", - "{\"a\":4}", - "{\"a\":5}", - "{\"a\":6}", - ] +def test_data_multiline [--nuon] { + let lines = if $nuon { + [ + "{a: 1}", + "{a: 2}", + "{a: 3}", + "{a: 4}", + "{a: 5}", + "{a: 6}", + ] + } else { + [ + "{\"a\":1}", + "{\"a\":2}", + "{\"a\":3}", + "{\"a\":4}", + "{\"a\":5}", + "{\"a\":6}", + ] + } if $nu.os-info.name == "windows" { $lines | str join "\r\n" @@ -84,3 +95,36 @@ def to_jsonl_single_object [] { let expect = "{\"a\":1}" assert equal $result $expect "could not convert to JSONL" } + +#[test] +def from_ndnuon_multiple_objects [] { + let result = test_data_multiline | from ndnuon + let expect = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_single_object [] { + let result = '{a: 1}' | from ndnuon + let expect = [{a:1}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_invalid_object [] { + assert error { '{"a":1' | formats from ndnuon } +} + +#[test] +def to_ndnuon_multiple_objects [] { + let result = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] | to ndnuon | str trim + let expect = test_data_multiline --nuon + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_single_object [] { + let result = [{a:1}] | to ndnuon | str trim + let expect = "{a: 1}" + assert equal $result $expect "could not convert to NDNUON" +} diff --git a/crates/nu-std/tests/test_std_formats.nu b/crates/nu-std/tests/test_std_formats.nu index f23590c5a0..ed6b079d7e 100644 --- a/crates/nu-std/tests/test_std_formats.nu +++ b/crates/nu-std/tests/test_std_formats.nu @@ -1,16 +1,27 @@ # Test std/formats when importing `use std *` use std * -def test_data_multiline [] { +def test_data_multiline [--nuon] { use std * - let lines = [ - "{\"a\":1}", - "{\"a\":2}", - "{\"a\":3}", - "{\"a\":4}", - "{\"a\":5}", - "{\"a\":6}", - ] + let lines = if $nuon { + [ + "{a: 1}", + "{a: 2}", + "{a: 3}", + "{a: 4}", + "{a: 5}", + "{a: 6}", + ] + } else { + [ + "{\"a\":1}", + "{\"a\":2}", + "{\"a\":3}", + "{\"a\":4}", + "{\"a\":5}", + "{\"a\":6}", + ] + } if $nu.os-info.name == "windows" { $lines | str join "\r\n" @@ -84,3 +95,36 @@ def to_jsonl_single_object [] { let expect = "{\"a\":1}" assert equal $result $expect "could not convert to JSONL" } + +#[test] +def from_ndnuon_multiple_objects [] { + let result = test_data_multiline | formats from ndnuon + let expect = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_single_object [] { + let result = '{a: 1}' | formats from ndnuon + let expect = [{a:1}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_invalid_object [] { + assert error { '{"a":1' | formats from ndnuon } +} + +#[test] +def to_ndnuon_multiple_objects [] { + let result = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] | formats to ndnuon | str trim + let expect = test_data_multiline --nuon + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_single_object [] { + let result = [{a:1}] | formats to ndnuon | str trim + let expect = "{a: 1}" + assert equal $result $expect "could not convert to NDNUON" +} From 1c18e37a7cdc1e7746f068a445521a3bbf3a0179 Mon Sep 17 00:00:00 2001 From: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:52:47 -0500 Subject: [PATCH 14/99] Always populate config record during startup (#14435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description As a bit of a follow-on to #13802 and #14249, this (pretty much a "one-line" change) really does *always* populate the `$env.config` record with the `nu-protocol::config` defaults during startup. This means that an `$env.config` record is value (with defaults) even during: * `nu -n` to suppress loading of config files * `nu -c ` * `nu